From 5d2b6a0f9230ff97ea8774eac0b4f55c191ec4ac Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 10 Sep 2026 19:40:25 +0800 Subject: [PATCH 01/33] added:ftp code file --- .../StorageModule_Config/Config_Define.h | 3 + .../Config_Json/Config_Json.cpp | 7 +- XEngine_Source/VSCopy_x86.bat | 3 +- XEngine_Source/XEngine_StorageApp/Makefile | 4 +- .../XEngine_StorageApp/StorageApp_FTP.cpp | 59 +++++++++++++++++ .../XEngine_StorageApp/StorageApp_FTP.h | 14 ++++ .../XEngine_StorageApp/StorageApp_Hdr.h | 9 +++ .../XEngine_StorageApp/StorageApp_Network.cpp | 65 ++++++++++++++++++- .../XEngine_StorageApp/StorageApp_Network.h | 8 +++ .../XEngine_StorageApp/XEngine_StorageApp.cpp | 46 +++++++++++++ .../XEngine_StorageApp.vcxproj | 2 + .../XEngine_StorageApp.vcxproj.filters | 6 ++ 12 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp create mode 100644 XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h diff --git a/XEngine_Source/StorageModule_Config/Config_Define.h b/XEngine_Source/StorageModule_Config/Config_Define.h index 1f74a8f6..fd81e574 100644 --- a/XEngine_Source/StorageModule_Config/Config_Define.h +++ b/XEngine_Source/StorageModule_Config/Config_Define.h @@ -20,6 +20,7 @@ typedef struct tag_XEngine_ServerConfig bool bReuseraddr; int nCenterPort; int nWebdavPort; + int nFTPPort; int nStorageDLPort; int nStorageUPPort; struct @@ -35,6 +36,7 @@ typedef struct tag_XEngine_ServerConfig int nWebdavThread; int nStorageDLThread; int nStorageUPThread; + int nFTPThread; }st_XMax; struct { @@ -44,6 +46,7 @@ typedef struct tag_XEngine_ServerConfig int nCenterTimeOut; int nStorageTimeOut; int nWebdavTimeOut; + int nFTPTimeOut; }st_XTime; struct { diff --git a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp index 2f3ec423..461b138c 100644 --- a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp +++ b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp @@ -88,8 +88,9 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->nWebdavPort = st_JsonRoot["nWebdavPort"].asInt(); pSt_ServerConfig->nStorageDLPort = st_JsonRoot["nStorageDLPort"].asInt(); pSt_ServerConfig->nStorageUPPort = st_JsonRoot["nStorageUPPort"].asInt(); + pSt_ServerConfig->nFTPPort = st_JsonRoot["nFTPPort"].asInt(); - if (st_JsonRoot["XMax"].empty() || (7 != st_JsonRoot["XMax"].size())) + if (st_JsonRoot["XMax"].empty() || (8 != st_JsonRoot["XMax"].size())) { Config_IsErrorOccur = true; Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_XMAX; @@ -103,8 +104,9 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->st_XMax.nStorageUPThread = st_JsonXMax["nStorageUPThread"].asInt(); pSt_ServerConfig->st_XMax.nStorageDLThread = st_JsonXMax["nStorageDLThread"].asInt(); pSt_ServerConfig->st_XMax.nWebdavThread = st_JsonXMax["nWebdavThread"].asInt(); + pSt_ServerConfig->st_XMax.nFTPThread = st_JsonXMax["nFTPThread"].asInt(); - if (st_JsonRoot["XTime"].empty() || (6 != st_JsonRoot["XTime"].size())) + if (st_JsonRoot["XTime"].empty() || (7 != st_JsonRoot["XTime"].size())) { Config_IsErrorOccur = true; Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_XTIME; @@ -117,6 +119,7 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->st_XTime.nCenterTimeOut = st_JsonXTime["nCenterTimeOut"].asInt(); pSt_ServerConfig->st_XTime.nStorageTimeOut = st_JsonXTime["nStorageTimeOut"].asInt(); pSt_ServerConfig->st_XTime.nWebdavTimeOut = st_JsonXTime["nWebdavTimeOut"].asInt(); + pSt_ServerConfig->st_XTime.nFTPTimeOut = st_JsonXTime["nFTPTimeOut"].asInt(); if (st_JsonRoot["XLog"].empty() || (5 != st_JsonRoot["XLog"].size())) { diff --git a/XEngine_Source/VSCopy_x86.bat b/XEngine_Source/VSCopy_x86.bat index 933255c5..93ef41a0 100644 --- a/XEngine_Source/VSCopy_x86.bat +++ b/XEngine_Source/VSCopy_x86.bat @@ -16,4 +16,5 @@ copy /y "%XEngine_Lib32%\XEngine_NetHelp\NetHelp_XSocket.dll" "./" copy /y "%XEngine_Lib32%\XEngine_HelpComponents\HelpComponents_DataBase.dll" "./" copy /y "%XEngine_Lib32%\XEngine_HelpComponents\HelpComponents_XLog.dll" "./" -copy /y "%XEngine_Lib32%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" \ No newline at end of file +copy /y "%XEngine_Lib32%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" +copy /y "%XEngine_Lib32%\XEngine_RfcComponents\RfcComponents_FTPProtocol.dll" "./" \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/Makefile b/XEngine_Source/XEngine_StorageApp/Makefile index cef0ef70..bdeb0de1 100644 --- a/XEngine_Source/XEngine_StorageApp/Makefile +++ b/XEngine_Source/XEngine_StorageApp/Makefile @@ -8,7 +8,7 @@ LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -ljsoncpp -ltinyxml2 -lXEngine_InfoReport -lXEngine_Verification LIBEX = OBJECTS = Storage_TaskPass.o Storage_TaskManage.o Storage_TaskP2p.o Storage_TaskAction.o \ - StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o XEngine_StorageApp.o + StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o XStorageApp_FTP.o Engine_StorageApp.o ifeq ($(RELEASE),1) FLAGS = -c -D _RELEASE @@ -61,6 +61,8 @@ StorageApp_UPLoader.o:./StorageApp_UPLoader.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./StorageApp_UPLoader.cpp StorageApp_Webdav.o:./StorageApp_Webdav.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./StorageApp_Webdav.cpp +XStorageApp_FTP.o:./XStorageApp_FTP.cpp + $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./XStorageApp_FTP.cpp Storage_TaskPass.o:./Storage_APPTask/Storage_TaskPass.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./Storage_APPTask/Storage_TaskPass.cpp diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp new file mode 100644 index 00000000..16de3e36 --- /dev/null +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -0,0 +1,59 @@ +#include "StorageApp_Hdr.h" + +XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam) +{ + int nThreadPos = *(int*)lParam; + nThreadPos++; + + while (bIsRun) + { + //等待指定线程事件触发 + if (FTPProtocol_Parse_EventWaitEx(xhFTPPacket, nThreadPos)) + { + int nListCount = 0; + XENGINE_MANAGEPOOL_TASKEVENT** ppSt_PKTClient; + //获取当前队列池中所有触发上传客户端 + FTPProtocol_Parse_GetPoolEx(xhFTPPacket, nThreadPos, &ppSt_PKTClient, &nListCount); + for (int i = 0; i < nListCount; i++) + { + for (int j = 0; j < ppSt_PKTClient[i]->nPktCount; j++) + { + XENGINE_KEYVALUE st_KeyValue = {}; + //获得指定上传客户端触发信息 + if (FTPProtocol_Parse_GetClientEx(xhFTPPacket, ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue)) + { + XEngine_Task_FTP(ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue); + } + } + } + BaseLib_Memory_Free((XPPPMEM)&ppSt_PKTClient, nListCount); + } + } + return 0; +} + +bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue) +{ + int nSDLen = 8192; + int nRVLen = 8192; + XCHAR tszSDBuffer[8192] = {}; + XCHAR tszRVBuffer[8192] = {}; + + if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_USER, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_USER))) + { + //用于心跳 + LPCXSTR lpszHdrBuffer = _X("Allow: OPTIONS POST GET PUT PROPFIND PROPPATCH DELETE LOCK UNLOCK MKCOL\r\n"); + FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("WEBDAV客户端:%s,请求OPTIONS心跳方法成功"), lpszClientAddr); + } + else + { + FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,发送的方法不支持"), lpszClientAddr); + return false; + } + + return true; +} \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h new file mode 100644 index 00000000..0cba01c2 --- /dev/null +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h @@ -0,0 +1,14 @@ +#pragma once +/******************************************************************** +// Created: 2026/09/10 17:37:00 +// File Name: D:\XEngine_Storage\XEngine_Source\XEngine_StorageApp\StorageApp_FTP.h +// File Path: D:\XEngine_Storage\XEngine_Source\XEngine_StorageApp +// File Base: StorageApp_FTP +// File Ext: h +// Project: XEngine +// Author: qyt +// Purpose: FTP服务端 +// History: +*********************************************************************/ +XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam); +bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h index bcfe1b24..a8cb666b 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h @@ -49,6 +49,8 @@ using namespace std; #include #include #include +#include +#include #include #include #include "../XStorage_Protocol.h" @@ -77,6 +79,7 @@ using namespace std; #define STORAGE_NETTYPE_HTTPDOWNLOAD 2 #define STORAGE_NETTYPE_HTTPCENTER 3 #define STORAGE_NETTYPE_HTTPWEBDAV 4 +#define STORAGE_NETTYPE_FTP 5 extern bool bIsRun; extern bool bIsTest; @@ -86,16 +89,19 @@ extern XHANDLE xhHBDownload; extern XHANDLE xhHBUPLoader; extern XHANDLE xhHBCenter; extern XHANDLE xhHBWebdav; +extern XHANDLE xhHBFTP; extern XHANDLE xhNetDownload; extern XHANDLE xhNetUPLoader; extern XHANDLE xhNetCenter; extern XHANDLE xhNetWebdav; +extern XHANDLE xhNetFTP; extern XHANDLE xhUPPool; extern XHANDLE xhDLPool; extern XHANDLE xhCTPool; extern XHANDLE xhWDPool; +extern XHANDLE xhFTPPool; extern XHANDLE xhDLSsl; extern XHANDLE xhUPSsl; @@ -107,6 +113,7 @@ extern XHANDLE xhUPHttp; extern XHANDLE xhDLHttp; extern XHANDLE xhCenterHttp; extern XHANDLE xhWebdavHttp; +extern XHANDLE xhFTPPacket; extern XSOCKET hBroadSocket; extern shared_ptr pSTDThread; @@ -122,6 +129,7 @@ extern XENGINE_LBCONFIG st_LoadbalanceCfg; #include "StorageApp_UPLoader.h" #include "StorageApp_Center.h" #include "StorageApp_Webdav.h" +#include "StorageApp_FTP.h" #include "Storage_APPTask/Storage_TaskPass.h" #include "Storage_APPTask/Storage_TaskP2p.h" #include "Storage_APPTask/Storage_TaskManage.h" @@ -139,6 +147,7 @@ extern XENGINE_LBCONFIG st_LoadbalanceCfg; #pragma comment(lib,"XEngine_HelpComponents/HelpComponents_XLog.lib") #pragma comment(lib,"XEngine_HelpComponents/HelpComponents_Packets.lib") #pragma comment(lib,"XEngine_RfcComponents/RfcComponents_HttpProtocol.lib") +#pragma comment(lib,"XEngine_RfcComponents/RfcComponents_FTPProtocol.lib") #pragma comment(lib,"XEngine_SystemSdk/XEngine_SystemApi.lib") #ifdef _DEBUG #ifdef _M_X64 diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp index 78206984..4240fd51 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp @@ -151,7 +151,7 @@ void XCALLBACK XEngine_Callback_WebdavRecv(LPCXSTR lpszClientAddr, XSOCKET hSock } else { - if (!HttpProtocol_Server_InserQueueEx(xhWebdavHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhWebdavHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("WEBDAV客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -165,6 +165,35 @@ void XCALLBACK XEngine_Callback_WebdavLeave(LPCXSTR lpszClientAddr, XSOCKET hSoc XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_HTTPWEBDAV); } ////////////////////////////////////////////////////////////////////////// +bool XCALLBACK XEngine_Callback_FTPLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +{ + FTPProtocol_Parse_CreateClientEx(xhFTPPacket, lpszClientAddr, 0); + SocketOpt_HeartBeat_InsertAddrEx(xhHBFTP, lpszClientAddr); + + int nSDLen = 0; + XCHAR tszSDBuffer[XPATH_MIN] = {}; + XENGINE_KEYVALUE st_KeyValue = {}; + + FTPProtocol_Parse_SendPacketEx(xhFTPPacket, XENGINE_FTPROTOCOL_RESPONSE_220, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,进入了服务器"), lpszClientAddr); + return true; +} +void XCALLBACK XEngine_Callback_FTPRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam) +{ + if (!FTPProtocol_Parse_InsertQueueEx(xhFTPPacket, lpszClientAddr, lpszRecvMsg, nMsgLen)) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, FTPProtocol_GetLastError()); + return; + } + SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTP, lpszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); +} +void XCALLBACK XEngine_Callback_FTPLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +{ + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTP); +} +////////////////////////////////////////////////////////////////////////// void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) { XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_HTTPDOWNLOAD); @@ -181,6 +210,10 @@ void XCALLBACK XEngine_Callback_HBWebdav(LPCXSTR lpszClientAddr, XSOCKET hSocket { XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_HTTPWEBDAV); } +void XCALLBACK XEngine_Callback_HBFTP(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) +{ + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTP); +} ////////////////////////////////////////////////////////////////////////// /* * 统一处理客户端离线后的资源清理逻辑。 @@ -308,6 +341,28 @@ bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClient HttpProtocol_Server_CloseClinetEx(xhWebdavHttp, lpszClientAddr); Cryption_Server_CloseClientEx(xhWDSsl, lpszClientAddr); } + else if (STORAGE_NETTYPE_FTP == nClientType) + { + m_StrClient = _X("FTP客户端"); + if (STORAGE_LEAVETYPE_HEARTBEAT == nLeaveType) + { + m_StrLeaveMsg = _X("心跳超时"); + NetCore_TCPXCore_CloseForClientEx(xhNetFTP, lpszClientAddr); + } + else if (STORAGE_LEAVETYPE_BYSELF == nLeaveType) + { + m_StrLeaveMsg = _X("被动断开"); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTP, lpszClientAddr); + } + else + { + m_StrLeaveMsg = _X("主动关闭"); + NetCore_TCPXCore_CloseForClientEx(xhNetFTP, lpszClientAddr); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTP, lpszClientAddr); + } + FTPProtocol_Parse_DeleteClientEx(xhFTPPacket, lpszClientAddr); + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("%s:%s,与服务器断开,原因:%s"), m_StrClient.c_str(), lpszClientAddr, m_StrLeaveMsg.c_str()); return true; } @@ -396,6 +451,14 @@ bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsg SocketOpt_HeartBeat_ActiveAddrEx(xhHBWebdav, lpszClientAddr); } } + else if (STORAGE_NETTYPE_FTP == nType) + { + bRet = NetCore_TCPXCore_SendEx(xhNetFTP, lpszClientAddr, lpszMsgBuffer, nMsgLen); + if (bRet && st_ServiceCfg.st_XTime.bHBTime) + { + SocketOpt_HeartBeat_ActiveAddrEx(xhHBWebdav, lpszClientAddr); + } + } if (!bRet) { XLONG dwRet = NetCore_GetLastError(); diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h index 643625dd..7430312b 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h @@ -3,20 +3,28 @@ bool XCALLBACK XEngine_Callback_DownloadLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); void XCALLBACK XEngine_Callback_DownloadRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_DownloadLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); + bool XCALLBACK XEngine_Callback_UPLoaderLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); void XCALLBACK XEngine_Callback_UPLoaderRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_UPLoaderLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); + bool XCALLBACK XEngine_Callback_CenterLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); void XCALLBACK XEngine_Callback_CenterRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_CenterLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); + bool XCALLBACK XEngine_Callback_WebdavLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); void XCALLBACK XEngine_Callback_WebdavRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_WebdavLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); +bool XCALLBACK XEngine_Callback_FTPLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); + void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBUPLoader(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBCenter(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBWebdav(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); +void XCALLBACK XEngine_Callback_HBFTP(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClientType); bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsgLen, int nType); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp index 28417a8e..57c93652 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp @@ -8,16 +8,19 @@ XHANDLE xhHBDownload = NULL; XHANDLE xhHBUPLoader = NULL; XHANDLE xhHBCenter = NULL; XHANDLE xhHBWebdav = NULL; +XHANDLE xhHBFTP = NULL; XHANDLE xhNetDownload = NULL; XHANDLE xhNetUPLoader = NULL; XHANDLE xhNetCenter = NULL; XHANDLE xhNetWebdav = NULL; +XHANDLE xhNetFTP = NULL; XHANDLE xhUPPool = NULL; XHANDLE xhDLPool = NULL; XHANDLE xhCTPool = NULL; XHANDLE xhWDPool = NULL; +XHANDLE xhFTPPool = NULL; XHANDLE xhDLSsl = NULL; XHANDLE xhUPSsl = NULL; @@ -29,6 +32,7 @@ XHANDLE xhUPHttp = NULL; XHANDLE xhDLHttp = NULL; XHANDLE xhCenterHttp = NULL; XHANDLE xhWebdavHttp = NULL; +XHANDLE xhFTPPacket = NULL; XSOCKET hBroadSocket = 0; shared_ptr pSTDThread = NULL; @@ -48,6 +52,7 @@ void ServiceApp_Stop(int signo) HttpProtocol_Server_DestroyEx(xhDLHttp); HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); + FTPProtocol_Parse_DestroyEx(xhFTPPacket); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); @@ -174,11 +179,13 @@ int main(int argc, char** argv) int nRet = -1; LPCXSTR lpszHTTPMime = _X("./XEngine_Config/HttpMime.types"); LPCXSTR lpszHTTPCode = _X("./XEngine_Config/HttpCode.types"); + LPCXSTR lpszFTPCodes = _X("./XEngine_Config/FTPCode.types"); HELPCOMPONENTS_XLOG_CONFIGURE st_XLogConfig; THREADPOOL_PARAMENT** ppSt_ListUPThread; THREADPOOL_PARAMENT** ppSt_ListDLThread; THREADPOOL_PARAMENT** ppSt_ListCTThread; THREADPOOL_PARAMENT** ppSt_ListWDThread; + THREADPOOL_PARAMENT** ppSt_ListFTPThread; memset(&st_XLogConfig, '\0', sizeof(HELPCOMPONENTS_XLOG_CONFIGURE)); memset(&st_ServiceCfg, '\0', sizeof(XENGINE_SERVERCONFIG)); @@ -539,6 +546,44 @@ int main(int argc, char** argv) } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动WEBDAV任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nWebdavThread); } + //FTP + if (st_ServiceCfg.nFTPPort > 0) + { + xhFTPPacket = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPPacket) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,初始化FTP服务失败,错误:%lX"), FTPProtocol_GetLastError()); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP服务成功,IO线程个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); + + xhNetFTP = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); + if (NULL == xhNetFTP) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPPort, NetCore_GetLastError()); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nIOThread); + NetCore_TCPXCore_RegisterCallBackEx(xhNetFTP, XEngine_Callback_FTPLogin, XEngine_Callback_FTPRecv, XEngine_Callback_FTPLeave); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,注册FTP网络服务事件成功!")); + + BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListFTPThread, st_ServiceCfg.st_XMax.nFTPThread, sizeof(THREADPOOL_PARAMENT)); + for (int i = 0; i < st_ServiceCfg.st_XMax.nFTPThread; i++) + { + int* pInt_Pos = new int; + *pInt_Pos = i; + + ppSt_ListFTPThread[i]->lParam = pInt_Pos; + ppSt_ListFTPThread[i]->fpCall_ThreadsTask = XEngine_FTP_Thread; + } + xhFTPPool = ManagePool_Thread_NQCreate(&ppSt_ListFTPThread, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPPool) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP处理线程池失败,错误:%d"), errno); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); + } //只有使用了数据库,才启用P2P if (st_ServiceCfg.st_P2xp.bEnable) { @@ -645,6 +690,7 @@ int main(int argc, char** argv) HttpProtocol_Server_DestroyEx(xhDLHttp); HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); + FTPProtocol_Parse_DestroyEx(xhFTPPacket); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj index 53fb21cc..1ca7c084 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj @@ -225,6 +225,7 @@ + @@ -239,6 +240,7 @@ + diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj.filters b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj.filters index 050ad75e..81869890 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj.filters +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj.filters @@ -57,6 +57,9 @@ 源文件 + + 源文件 + @@ -95,5 +98,8 @@ 头文件 + + 头文件 + \ No newline at end of file From 908c86627ecfa85210aa3d8d9102aa2bd359f5a2 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Fri, 11 Sep 2026 12:51:38 +0800 Subject: [PATCH 02/33] added:ftp data transfer --- .../StorageModule_Config/Config_Define.h | 3 +- .../Config_Json/Config_Json.cpp | 3 +- XEngine_Source/VSCopy_Debug.bat | 3 +- .../XEngine_StorageApp/StorageApp_FTP.cpp | 87 ++++++++++++--- .../XEngine_StorageApp/StorageApp_FTP.h | 5 +- .../XEngine_StorageApp/StorageApp_Hdr.h | 18 +++- .../XEngine_StorageApp/StorageApp_Network.cpp | 100 ++++++++++++++---- .../XEngine_StorageApp/StorageApp_Network.h | 13 ++- .../XEngine_StorageApp/XEngine_StorageApp.cpp | 95 +++++++++++++---- 9 files changed, 259 insertions(+), 68 deletions(-) diff --git a/XEngine_Source/StorageModule_Config/Config_Define.h b/XEngine_Source/StorageModule_Config/Config_Define.h index fd81e574..d10690a4 100644 --- a/XEngine_Source/StorageModule_Config/Config_Define.h +++ b/XEngine_Source/StorageModule_Config/Config_Define.h @@ -20,7 +20,8 @@ typedef struct tag_XEngine_ServerConfig bool bReuseraddr; int nCenterPort; int nWebdavPort; - int nFTPPort; + int nFTPCPort; + int nFTPDPort; int nStorageDLPort; int nStorageUPPort; struct diff --git a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp index 461b138c..b142b8c1 100644 --- a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp +++ b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp @@ -88,7 +88,8 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->nWebdavPort = st_JsonRoot["nWebdavPort"].asInt(); pSt_ServerConfig->nStorageDLPort = st_JsonRoot["nStorageDLPort"].asInt(); pSt_ServerConfig->nStorageUPPort = st_JsonRoot["nStorageUPPort"].asInt(); - pSt_ServerConfig->nFTPPort = st_JsonRoot["nFTPPort"].asInt(); + pSt_ServerConfig->nFTPCPort = st_JsonRoot["nFTPCPort"].asInt(); + pSt_ServerConfig->nFTPDPort = st_JsonRoot["nFTPDPort"].asInt(); if (st_JsonRoot["XMax"].empty() || (8 != st_JsonRoot["XMax"].size())) { diff --git a/XEngine_Source/VSCopy_Debug.bat b/XEngine_Source/VSCopy_Debug.bat index 654185d3..c0dd8f28 100644 --- a/XEngine_Source/VSCopy_Debug.bat +++ b/XEngine_Source/VSCopy_Debug.bat @@ -16,4 +16,5 @@ copy /y "D:\XEngine\XEngine_SourceCode\Debug\NetHelp_XSocket.dll" "./" copy /y "D:\XEngine\XEngine_SourceCode\Debug\HelpComponents_DataBase.dll" "./" copy /y "D:\XEngine\XEngine_SourceCode\Debug\HelpComponents_XLog.dll" "./" -copy /y "D:\XEngine\XEngine_SourceCode\Debug\RfcComponents_HttpProtocol.dll" "./" \ No newline at end of file +copy /y "D:\XEngine\XEngine_SourceCode\Debug\RfcComponents_HttpProtocol.dll" "./" +copy /y "D:\XEngine\XEngine_SourceCode\Debug\RfcComponents_FTPProtocol.dll" "./" \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 16de3e36..d8100f64 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -1,6 +1,6 @@ #include "StorageApp_Hdr.h" -XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam) +XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam) { int nThreadPos = *(int*)lParam; nThreadPos++; @@ -8,21 +8,21 @@ XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam) while (bIsRun) { //等待指定线程事件触发 - if (FTPProtocol_Parse_EventWaitEx(xhFTPPacket, nThreadPos)) + if (FTPProtocol_Parse_EventWaitEx(xhFTPContral, nThreadPos)) { int nListCount = 0; XENGINE_MANAGEPOOL_TASKEVENT** ppSt_PKTClient; //获取当前队列池中所有触发上传客户端 - FTPProtocol_Parse_GetPoolEx(xhFTPPacket, nThreadPos, &ppSt_PKTClient, &nListCount); + FTPProtocol_Parse_GetPoolEx(xhFTPContral, nThreadPos, &ppSt_PKTClient, &nListCount); for (int i = 0; i < nListCount; i++) { for (int j = 0; j < ppSt_PKTClient[i]->nPktCount; j++) { XENGINE_KEYVALUE st_KeyValue = {}; //获得指定上传客户端触发信息 - if (FTPProtocol_Parse_GetClientEx(xhFTPPacket, ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue)) + if (FTPProtocol_Parse_GetClientEx(xhFTPContral, ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue)) { - XEngine_Task_FTP(ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue); + XEngine_Task_FTP(ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue, STORAGE_NETTYPE_FTPCONTRAL); } } } @@ -31,8 +31,39 @@ XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam) } return 0; } +XHTHREAD XCALLBACK XEngine_FTPDatas_Thread(XPVOID lParam) +{ + int nThreadPos = *(int*)lParam; + nThreadPos++; -bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue) + while (bIsRun) + { + //等待指定线程事件触发 + if (FTPProtocol_Parse_EventWaitEx(xhFTPDatas, nThreadPos)) + { + int nListCount = 0; + XENGINE_MANAGEPOOL_TASKEVENT** ppSt_PKTClient; + //获取当前队列池中所有触发上传客户端 + FTPProtocol_Parse_GetPoolEx(xhFTPDatas, nThreadPos, &ppSt_PKTClient, &nListCount); + for (int i = 0; i < nListCount; i++) + { + for (int j = 0; j < ppSt_PKTClient[i]->nPktCount; j++) + { + XENGINE_KEYVALUE st_KeyValue = {}; + //获得指定上传客户端触发信息 + if (FTPProtocol_Parse_GetClientEx(xhFTPDatas, ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue)) + { + XEngine_Task_FTP(ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue, STORAGE_NETTYPE_FTPDATAS); + } + } + } + BaseLib_Memory_Free((XPPPMEM)&ppSt_PKTClient, nListCount); + } + } + return 0; +} + +bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, int nNetType) { int nSDLen = 8192; int nRVLen = 8192; @@ -41,17 +72,47 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue) if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_USER, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_USER))) { - //用于心跳 - LPCXSTR lpszHdrBuffer = _X("Allow: OPTIONS POST GET PUT PROPFIND PROPPATCH DELETE LOCK UNLOCK MKCOL\r\n"); - FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); - XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("WEBDAV客户端:%s,请求OPTIONS心跳方法成功"), lpszClientAddr); + //用户请求 + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_331, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求用户名成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PASS, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PASS))) + { + //密码请求 + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_230, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求密码成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_TYPE, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_TYPE))) + { + //类型请求 + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_200, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求类型成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PASV, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PASV))) + { + //被动模式请求 + int nPort = 0; + XCHAR tszIPAddr[XPATH_MIN] = {}; + APIADDR_IPADDR st_IPAddr = {}; + + APIAddr_IPAddr_IsIPV4Addr(st_ServiceCfg.tszIPAddr, &st_IPAddr); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, &nRVLen, false); + + int p1 = st_ServiceCfg.nFTPDPort / 256; + int p2 = st_ServiceCfg.nFTPDPort % 256; + _xstprintf(tszSDBuffer, _X("%d %s (%d,%d,%d,%d,%d,%d)"), XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, st_IPAddr.nIPAddr1, st_IPAddr.nIPAddr2, st_IPAddr.nIPAddr3, st_IPAddr.nIPAddr4, p1, p2); + + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求被动模式成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); } else { FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); - XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,发送的方法不支持"), lpszClientAddr); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,发送的方法 %s:%s 不支持"), lpszClientAddr, pSt_KeyValue->tszStrKey, pSt_KeyValue->tszStrVlu); return false; } diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h index 0cba01c2..4926241c 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h @@ -10,5 +10,6 @@ // Purpose: FTP服务端 // History: *********************************************************************/ -XHTHREAD XCALLBACK XEngine_FTP_Thread(XPVOID lParam); -bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue); \ No newline at end of file +XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam); +XHTHREAD XCALLBACK XEngine_FTPDatas_Thread(XPVOID lParam); +bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE* pSt_KeyValue, int nNetType); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h index a8cb666b..9f312795 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h @@ -41,6 +41,8 @@ using namespace std; #include #include #include +#include +#include #include #include #include @@ -79,7 +81,8 @@ using namespace std; #define STORAGE_NETTYPE_HTTPDOWNLOAD 2 #define STORAGE_NETTYPE_HTTPCENTER 3 #define STORAGE_NETTYPE_HTTPWEBDAV 4 -#define STORAGE_NETTYPE_FTP 5 +#define STORAGE_NETTYPE_FTPCONTRAL 5 +#define STORAGE_NETTYPE_FTPDATAS 6 extern bool bIsRun; extern bool bIsTest; @@ -89,19 +92,22 @@ extern XHANDLE xhHBDownload; extern XHANDLE xhHBUPLoader; extern XHANDLE xhHBCenter; extern XHANDLE xhHBWebdav; -extern XHANDLE xhHBFTP; +extern XHANDLE xhHBFTPContral; +extern XHANDLE xhHBFTPDatas; extern XHANDLE xhNetDownload; extern XHANDLE xhNetUPLoader; extern XHANDLE xhNetCenter; extern XHANDLE xhNetWebdav; -extern XHANDLE xhNetFTP; +extern XHANDLE xhNetFTPContral; +extern XHANDLE xhNetFTPDatas; extern XHANDLE xhUPPool; extern XHANDLE xhDLPool; extern XHANDLE xhCTPool; extern XHANDLE xhWDPool; -extern XHANDLE xhFTPPool; +extern XHANDLE xhFTPPoolContral; +extern XHANDLE xhFTPPoolDatas; extern XHANDLE xhDLSsl; extern XHANDLE xhUPSsl; @@ -113,7 +119,8 @@ extern XHANDLE xhUPHttp; extern XHANDLE xhDLHttp; extern XHANDLE xhCenterHttp; extern XHANDLE xhWebdavHttp; -extern XHANDLE xhFTPPacket; +extern XHANDLE xhFTPContral; +extern XHANDLE xhFTPDatas; extern XSOCKET hBroadSocket; extern shared_ptr pSTDThread; @@ -144,6 +151,7 @@ extern XENGINE_LBCONFIG st_LoadbalanceCfg; #pragma comment(lib,"XEngine_Core/XEngine_ManagePool.lib") #pragma comment(lib,"XEngine_Core/XEngine_Cryption.lib") #pragma comment(lib,"XEngine_Client/XClient_APIHelp.lib") +#pragma comment(lib,"XEngine_NetHelp/NetHelp_APIAddr.lib") #pragma comment(lib,"XEngine_HelpComponents/HelpComponents_XLog.lib") #pragma comment(lib,"XEngine_HelpComponents/HelpComponents_Packets.lib") #pragma comment(lib,"XEngine_RfcComponents/RfcComponents_HttpProtocol.lib") diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp index 4240fd51..38d7fa2b 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp @@ -165,33 +165,54 @@ void XCALLBACK XEngine_Callback_WebdavLeave(LPCXSTR lpszClientAddr, XSOCKET hSoc XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_HTTPWEBDAV); } ////////////////////////////////////////////////////////////////////////// -bool XCALLBACK XEngine_Callback_FTPLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +bool XCALLBACK XEngine_Callback_FTPContralLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) { - FTPProtocol_Parse_CreateClientEx(xhFTPPacket, lpszClientAddr, 0); - SocketOpt_HeartBeat_InsertAddrEx(xhHBFTP, lpszClientAddr); + FTPProtocol_Parse_CreateClientEx(xhFTPContral, lpszClientAddr, 0); + SocketOpt_HeartBeat_InsertAddrEx(xhHBFTPContral, lpszClientAddr); int nSDLen = 0; XCHAR tszSDBuffer[XPATH_MIN] = {}; XENGINE_KEYVALUE st_KeyValue = {}; - FTPProtocol_Parse_SendPacketEx(xhFTPPacket, XENGINE_FTPROTOCOL_RESPONSE_220, tszSDBuffer, &nSDLen); - XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTP); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_220, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,进入了服务器"), lpszClientAddr); return true; } -void XCALLBACK XEngine_Callback_FTPRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam) +void XCALLBACK XEngine_Callback_FTPContralRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam) { - if (!FTPProtocol_Parse_InsertQueueEx(xhFTPPacket, lpszClientAddr, lpszRecvMsg, nMsgLen)) + if (!FTPProtocol_Parse_InsertQueueEx(xhFTPContral, lpszClientAddr, lpszRecvMsg, nMsgLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, FTPProtocol_GetLastError()); return; } - SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTP, lpszClientAddr); + SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPContral, lpszClientAddr); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); } -void XCALLBACK XEngine_Callback_FTPLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +void XCALLBACK XEngine_Callback_FTPContralLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) { - XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTP); + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTPCONTRAL); +} +bool XCALLBACK XEngine_Callback_FTPDatasLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +{ + FTPProtocol_Parse_CreateClientEx(xhFTPDatas, lpszClientAddr, 0); + SocketOpt_HeartBeat_InsertAddrEx(xhHBFTPDatas, lpszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,进入了服务器"), lpszClientAddr); + return true; +} +void XCALLBACK XEngine_Callback_FTPDatasRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam) +{ + if (!FTPProtocol_Parse_InsertQueueEx(xhFTPDatas, lpszClientAddr, lpszRecvMsg, nMsgLen)) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, FTPProtocol_GetLastError()); + return; + } + SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPDatas, lpszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); +} +void XCALLBACK XEngine_Callback_FTPDatasLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) +{ + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTPDATAS); } ////////////////////////////////////////////////////////////////////////// void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) @@ -210,9 +231,13 @@ void XCALLBACK XEngine_Callback_HBWebdav(LPCXSTR lpszClientAddr, XSOCKET hSocket { XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_HTTPWEBDAV); } -void XCALLBACK XEngine_Callback_HBFTP(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) +void XCALLBACK XEngine_Callback_HBFTPContral(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) +{ + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTPCONTRAL); +} +void XCALLBACK XEngine_Callback_HBFTPDatas(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) { - XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTP); + XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTPDATAS); } ////////////////////////////////////////////////////////////////////////// /* @@ -341,26 +366,51 @@ bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClient HttpProtocol_Server_CloseClinetEx(xhWebdavHttp, lpszClientAddr); Cryption_Server_CloseClientEx(xhWDSsl, lpszClientAddr); } - else if (STORAGE_NETTYPE_FTP == nClientType) + else if (STORAGE_NETTYPE_FTPCONTRAL == nClientType) { m_StrClient = _X("FTP客户端"); if (STORAGE_LEAVETYPE_HEARTBEAT == nLeaveType) { m_StrLeaveMsg = _X("心跳超时"); - NetCore_TCPXCore_CloseForClientEx(xhNetFTP, lpszClientAddr); + NetCore_TCPXCore_CloseForClientEx(xhNetFTPContral, lpszClientAddr); } else if (STORAGE_LEAVETYPE_BYSELF == nLeaveType) { m_StrLeaveMsg = _X("被动断开"); - SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTP, lpszClientAddr); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPContral, lpszClientAddr); } else { m_StrLeaveMsg = _X("主动关闭"); - NetCore_TCPXCore_CloseForClientEx(xhNetFTP, lpszClientAddr); - SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTP, lpszClientAddr); + NetCore_TCPXCore_CloseForClientEx(xhNetFTPContral, lpszClientAddr); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPContral, lpszClientAddr); } - FTPProtocol_Parse_DeleteClientEx(xhFTPPacket, lpszClientAddr); + FTPProtocol_Parse_DeleteClientEx(xhFTPContral, lpszClientAddr); + } + else if (STORAGE_NETTYPE_FTPDATAS == nClientType) + { + m_StrClient = _X("FTP数据客户端"); + if (STORAGE_LEAVETYPE_HEARTBEAT == nLeaveType) + { + m_StrLeaveMsg = _X("心跳超时"); + NetCore_TCPXCore_CloseForClientEx(xhNetFTPDatas, lpszClientAddr); + } + else if (STORAGE_LEAVETYPE_BYSELF == nLeaveType) + { + m_StrLeaveMsg = _X("被动断开"); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPDatas, lpszClientAddr); + } + else + { + m_StrLeaveMsg = _X("主动关闭"); + NetCore_TCPXCore_CloseForClientEx(xhNetFTPDatas, lpszClientAddr); + SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPDatas, lpszClientAddr); + } + } + else + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("未知客户端类型:%s,无法处理离线逻辑"), lpszClientAddr); + return false; } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("%s:%s,与服务器断开,原因:%s"), m_StrClient.c_str(), lpszClientAddr, m_StrLeaveMsg.c_str()); @@ -451,12 +501,20 @@ bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsg SocketOpt_HeartBeat_ActiveAddrEx(xhHBWebdav, lpszClientAddr); } } - else if (STORAGE_NETTYPE_FTP == nType) + else if (STORAGE_NETTYPE_FTPCONTRAL == nType) { - bRet = NetCore_TCPXCore_SendEx(xhNetFTP, lpszClientAddr, lpszMsgBuffer, nMsgLen); + bRet = NetCore_TCPXCore_SendEx(xhNetFTPContral, lpszClientAddr, lpszMsgBuffer, nMsgLen); if (bRet && st_ServiceCfg.st_XTime.bHBTime) { - SocketOpt_HeartBeat_ActiveAddrEx(xhHBWebdav, lpszClientAddr); + SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPContral, lpszClientAddr); + } + } + else if (STORAGE_NETTYPE_FTPDATAS == nType) + { + bRet = NetCore_TCPXCore_SendEx(xhNetFTPDatas, lpszClientAddr, lpszMsgBuffer, nMsgLen); + if (bRet && st_ServiceCfg.st_XTime.bHBTime) + { + SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPDatas, lpszClientAddr); } } if (!bRet) diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h index 7430312b..f0d95235 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h @@ -16,15 +16,20 @@ bool XCALLBACK XEngine_Callback_WebdavLogin(LPCXSTR lpszClientAddr, XSOCKET hSoc void XCALLBACK XEngine_Callback_WebdavRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_WebdavLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); -bool XCALLBACK XEngine_Callback_FTPLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); -void XCALLBACK XEngine_Callback_FTPRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); -void XCALLBACK XEngine_Callback_FTPLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); +bool XCALLBACK XEngine_Callback_FTPContralLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPContralRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPContralLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); + +bool XCALLBACK XEngine_Callback_FTPDatasLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPDatasRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); +void XCALLBACK XEngine_Callback_FTPDatasLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBUPLoader(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBCenter(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBWebdav(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); -void XCALLBACK XEngine_Callback_HBFTP(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); +void XCALLBACK XEngine_Callback_HBFTPContral(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); +void XCALLBACK XEngine_Callback_HBFTPDatas(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClientType); bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsgLen, int nType); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp index 57c93652..6a7d0179 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp @@ -8,19 +8,22 @@ XHANDLE xhHBDownload = NULL; XHANDLE xhHBUPLoader = NULL; XHANDLE xhHBCenter = NULL; XHANDLE xhHBWebdav = NULL; -XHANDLE xhHBFTP = NULL; +XHANDLE xhHBFTPContral = NULL; +XHANDLE xhHBFTPDatas = NULL; XHANDLE xhNetDownload = NULL; XHANDLE xhNetUPLoader = NULL; XHANDLE xhNetCenter = NULL; XHANDLE xhNetWebdav = NULL; -XHANDLE xhNetFTP = NULL; +XHANDLE xhNetFTPContral = NULL; +XHANDLE xhNetFTPDatas = NULL; XHANDLE xhUPPool = NULL; XHANDLE xhDLPool = NULL; XHANDLE xhCTPool = NULL; XHANDLE xhWDPool = NULL; -XHANDLE xhFTPPool = NULL; +XHANDLE xhFTPPoolContral = NULL; +XHANDLE xhFTPPoolDatas = NULL; XHANDLE xhDLSsl = NULL; XHANDLE xhUPSsl = NULL; @@ -32,7 +35,8 @@ XHANDLE xhUPHttp = NULL; XHANDLE xhDLHttp = NULL; XHANDLE xhCenterHttp = NULL; XHANDLE xhWebdavHttp = NULL; -XHANDLE xhFTPPacket = NULL; +XHANDLE xhFTPContral = NULL; +XHANDLE xhFTPDatas = NULL; XSOCKET hBroadSocket = 0; shared_ptr pSTDThread = NULL; @@ -52,7 +56,8 @@ void ServiceApp_Stop(int signo) HttpProtocol_Server_DestroyEx(xhDLHttp); HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); - FTPProtocol_Parse_DestroyEx(xhFTPPacket); + FTPProtocol_Parse_DestroyEx(xhFTPContral); + FTPProtocol_Parse_DestroyEx(xhFTPDatas); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); @@ -63,16 +68,22 @@ void ServiceApp_Stop(int signo) NetCore_TCPXCore_DestroyEx(xhNetUPLoader); NetCore_TCPXCore_DestroyEx(xhNetCenter); NetCore_TCPXCore_DestroyEx(xhNetWebdav); + NetCore_TCPXCore_DestroyEx(xhNetFTPContral); + NetCore_TCPXCore_DestroyEx(xhNetFTPDatas); SocketOpt_HeartBeat_DestoryEx(xhHBDownload); SocketOpt_HeartBeat_DestoryEx(xhHBUPLoader); SocketOpt_HeartBeat_DestoryEx(xhHBCenter); SocketOpt_HeartBeat_DestoryEx(xhHBWebdav); + SocketOpt_HeartBeat_DestoryEx(xhHBFTPContral); + SocketOpt_HeartBeat_DestoryEx(xhHBFTPDatas); ManagePool_Thread_NQDestroy(xhUPPool); ManagePool_Thread_NQDestroy(xhDLPool); ManagePool_Thread_NQDestroy(xhCTPool); ManagePool_Thread_NQDestroy(xhWDPool); + ManagePool_Thread_NQDestroy(xhFTPPoolContral); + ManagePool_Thread_NQDestroy(xhFTPPoolDatas); Algorithm_Calculation_Close(xhLimit); HelpComponents_XLog_Destroy(xhLog); @@ -185,7 +196,8 @@ int main(int argc, char** argv) THREADPOOL_PARAMENT** ppSt_ListDLThread; THREADPOOL_PARAMENT** ppSt_ListCTThread; THREADPOOL_PARAMENT** ppSt_ListWDThread; - THREADPOOL_PARAMENT** ppSt_ListFTPThread; + THREADPOOL_PARAMENT** ppSt_ListFTPCThread; + THREADPOOL_PARAMENT** ppSt_ListFTPDThread; memset(&st_XLogConfig, '\0', sizeof(HELPCOMPONENTS_XLOG_CONFIGURE)); memset(&st_ServiceCfg, '\0', sizeof(XENGINE_SERVERCONFIG)); @@ -547,37 +559,73 @@ int main(int argc, char** argv) XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动WEBDAV任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nWebdavThread); } //FTP - if (st_ServiceCfg.nFTPPort > 0) + if (st_ServiceCfg.nFTPCPort > 0) { - xhFTPPacket = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); - if (NULL == xhFTPPacket) + //控制端口启用 + xhFTPContral = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPContral) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,初始化FTP服务失败,错误:%lX"), FTPProtocol_GetLastError()); goto XENGINE_EXITAPP; } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP服务成功,IO线程个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); - xhNetFTP = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); - if (NULL == xhNetFTP) + xhNetFTPContral = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPCPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); + if (NULL == xhNetFTPContral) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPPort, NetCore_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPCPort, NetCore_GetLastError()); goto XENGINE_EXITAPP; } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nIOThread); - NetCore_TCPXCore_RegisterCallBackEx(xhNetFTP, XEngine_Callback_FTPLogin, XEngine_Callback_FTPRecv, XEngine_Callback_FTPLeave); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPCPort, st_ServiceCfg.st_XMax.nIOThread); + NetCore_TCPXCore_RegisterCallBackEx(xhNetFTPContral, XEngine_Callback_FTPContralLogin, XEngine_Callback_FTPContralRecv, XEngine_Callback_FTPContralLeave); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,注册FTP网络服务事件成功!")); - BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListFTPThread, st_ServiceCfg.st_XMax.nFTPThread, sizeof(THREADPOOL_PARAMENT)); + BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListFTPCThread, st_ServiceCfg.st_XMax.nFTPThread, sizeof(THREADPOOL_PARAMENT)); for (int i = 0; i < st_ServiceCfg.st_XMax.nFTPThread; i++) { int* pInt_Pos = new int; *pInt_Pos = i; - ppSt_ListFTPThread[i]->lParam = pInt_Pos; - ppSt_ListFTPThread[i]->fpCall_ThreadsTask = XEngine_FTP_Thread; + ppSt_ListFTPCThread[i]->lParam = pInt_Pos; + ppSt_ListFTPCThread[i]->fpCall_ThreadsTask = XEngine_FTPContral_Thread; } - xhFTPPool = ManagePool_Thread_NQCreate(&ppSt_ListFTPThread, st_ServiceCfg.st_XMax.nFTPThread); - if (NULL == xhFTPPool) + xhFTPPoolContral = ManagePool_Thread_NQCreate(&ppSt_ListFTPCThread, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPPoolContral) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP处理线程池失败,错误:%d"), errno); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); + //数据端口启用 + xhFTPDatas = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPDatas) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,初始化FTP服务失败,错误:%lX"), FTPProtocol_GetLastError()); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP服务成功,IO线程个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); + + xhNetFTPDatas = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPDPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); + if (NULL == xhNetFTPDatas) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPDPort, NetCore_GetLastError()); + goto XENGINE_EXITAPP; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPDPort, st_ServiceCfg.st_XMax.nIOThread); + NetCore_TCPXCore_RegisterCallBackEx(xhNetFTPDatas, XEngine_Callback_FTPDatasLogin, XEngine_Callback_FTPDatasRecv, XEngine_Callback_FTPDatasLeave); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,注册FTP网络服务事件成功!")); + + BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListFTPDThread, st_ServiceCfg.st_XMax.nFTPThread, sizeof(THREADPOOL_PARAMENT)); + for (int i = 0; i < st_ServiceCfg.st_XMax.nFTPThread; i++) + { + int* pInt_Pos = new int; + *pInt_Pos = i; + + ppSt_ListFTPDThread[i]->lParam = pInt_Pos; + ppSt_ListFTPDThread[i]->fpCall_ThreadsTask = XEngine_FTPDatas_Thread; + } + xhFTPPoolDatas = ManagePool_Thread_NQCreate(&ppSt_ListFTPDThread, st_ServiceCfg.st_XMax.nFTPThread); + if (NULL == xhFTPPoolDatas) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP处理线程池失败,错误:%d"), errno); goto XENGINE_EXITAPP; @@ -690,7 +738,8 @@ int main(int argc, char** argv) HttpProtocol_Server_DestroyEx(xhDLHttp); HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); - FTPProtocol_Parse_DestroyEx(xhFTPPacket); + FTPProtocol_Parse_DestroyEx(xhFTPContral); + FTPProtocol_Parse_DestroyEx(xhFTPDatas); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); @@ -701,16 +750,22 @@ int main(int argc, char** argv) NetCore_TCPXCore_DestroyEx(xhNetUPLoader); NetCore_TCPXCore_DestroyEx(xhNetCenter); NetCore_TCPXCore_DestroyEx(xhNetWebdav); + NetCore_TCPXCore_DestroyEx(xhNetFTPContral); + NetCore_TCPXCore_DestroyEx(xhNetFTPDatas); SocketOpt_HeartBeat_DestoryEx(xhHBDownload); SocketOpt_HeartBeat_DestoryEx(xhHBUPLoader); SocketOpt_HeartBeat_DestoryEx(xhHBCenter); SocketOpt_HeartBeat_DestoryEx(xhHBWebdav); + SocketOpt_HeartBeat_DestoryEx(xhHBFTPContral); + SocketOpt_HeartBeat_DestoryEx(xhHBFTPDatas); ManagePool_Thread_NQDestroy(xhUPPool); ManagePool_Thread_NQDestroy(xhDLPool); ManagePool_Thread_NQDestroy(xhCTPool); ManagePool_Thread_NQDestroy(xhWDPool); + ManagePool_Thread_NQDestroy(xhFTPPoolContral); + ManagePool_Thread_NQDestroy(xhFTPPoolDatas); Algorithm_Calculation_Close(xhLimit); HelpComponents_XLog_Destroy(xhLog); From af2bf614d1e994cd8d9b63e825f557ee8ebc1c60 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Fri, 11 Sep 2026 15:28:23 +0800 Subject: [PATCH 03/33] modify:data transfer port for ftp --- .../StorageModule_APIHelp/APIHelp_Define.h | 61 +++++++- .../APIHelp_Port/APIHelp_Port.cpp | 132 ++++++++++++++++++ .../APIHelp_Port/APIHelp_Port.h | 34 +++++ XEngine_Source/StorageModule_APIHelp/Makefile | 4 +- .../StorageModule_APIHelp.def | 7 +- .../StorageModule_APIHelp.vcxproj | 2 + .../StorageModule_APIHelp.vcxproj.filters | 12 ++ XEngine_Source/StorageModule_APIHelp/pch.cpp | 21 +++ XEngine_Source/StorageModule_APIHelp/pch.h | 1 + .../StorageModule_Config/Config_Define.h | 3 +- .../Config_Json/Config_Json.cpp | 4 +- .../XEngine_StorageApp/StorageApp_FTP.cpp | 57 +++----- .../XEngine_StorageApp/StorageApp_FTP.h | 1 - .../XEngine_StorageApp/StorageApp_Hdr.h | 4 - .../XEngine_StorageApp/XEngine_StorageApp.cpp | 50 +------ 15 files changed, 300 insertions(+), 93 deletions(-) create mode 100644 XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.cpp create mode 100644 XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.h diff --git a/XEngine_Source/StorageModule_APIHelp/APIHelp_Define.h b/XEngine_Source/StorageModule_APIHelp/APIHelp_Define.h index ea12c6c7..479d544a 100644 --- a/XEngine_Source/StorageModule_APIHelp/APIHelp_Define.h +++ b/XEngine_Source/StorageModule_APIHelp/APIHelp_Define.h @@ -441,4 +441,63 @@ extern "C" bool APIHelp_Api_WDToUrl(LPCXSTR lpszUrl, XCHAR* ptszUrl); 意思:是否成功 备注: *********************************************************************/ -extern "C" bool APIHelp_Api_BSizeToStr(__int64x nSize, XCHAR* ptszSizeStr, int nType); \ No newline at end of file +extern "C" bool APIHelp_Api_BSizeToStr(__int64x nSize, XCHAR* ptszSizeStr, int nType); +/************************************************************************/ +/* 端口函数 */ +/************************************************************************/ +/******************************************************************** +函数名称:APIHelp_Port_Init +函数功能:初始化端口列表 + 参数.一:nPortStart + In/Out:In + 类型:整数型 + 可空:Y + 意思:可用起始端口 + 参数.二:nPortEnd + In/Out:In + 类型:整数型 + 可空:Y + 意思:可用结束端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool APIHelp_Port_Init(int nPortStart = 54000, int nPortEnd = 55000); +/******************************************************************** +函数名称:APIHelp_Port_Destroy +函数功能:销毁端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool APIHelp_Port_Destroy(); +/******************************************************************** +函数名称:APIHelp_Port_Get +函数功能:获取一个没有使用的端口 + 参数.一:pInt_Port + In/Out:Out + 类型:整数型指针 + 可空:N + 意思:输出获取到的端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool APIHelp_Port_Get(int* pInt_Port); +/******************************************************************** +函数名称:APIHelp_Port_Free +函数功能:还原一个端口 + 参数.一:nPort + In/Out:In + 类型:整数型 + 可空:N + 意思:输入要还原的端口号 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool APIHelp_Port_Free(int nPort); \ No newline at end of file diff --git a/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.cpp b/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.cpp new file mode 100644 index 00000000..18045084 --- /dev/null +++ b/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.cpp @@ -0,0 +1,132 @@ +#include "pch.h" +#include "APIHelp_Port.h" +/******************************************************************** +// Created: 2026/09/11 14:46:00 +// File Name: D:\XEngine_Storage\XEngine_Source\StorageModule_APIHelp\APIHelp_Port\APIHelp_Port.cpp +// File Path: D:\XEngine_Storage\XEngine_Source\StorageModule_APIHelp\APIHelp_Port +// File Base: APIHelp_Port +// File Ext: cpp +// Project: XEngine_Storage +// Repository: D:\XEngine_Storage (branch: develop) +// Author: qyt +// Purpose: 端口处理类 +// History: +*********************************************************************/ +CAPIHelp_Port::CAPIHelp_Port() +{ +} +CAPIHelp_Port::~CAPIHelp_Port() +{ +} +////////////////////////////////////////////////////////////////////////// +// 公有函数 +////////////////////////////////////////////////////////////////////////// +/******************************************************************** +函数名称:APIHelp_Port_Init +函数功能:初始化端口列表 + 参数.一:nPortStart + In/Out:In + 类型:整数型 + 可空:Y + 意思:可用起始端口 + 参数.二:nPortEnd + In/Out:In + 类型:整数型 + 可空:Y + 意思:可用结束端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CAPIHelp_Port::APIHelp_Port_Init(int nPortStart /* = 54000 */, int nPortEnd /* = 55000 */) +{ + APIHelp_IsErrorOccur = false; + + for (int i = nPortStart; i < nPortEnd; i++) + { + MODULEHELP_PORTINFO st_PortInfo = {}; + st_PortInfo.nPort = i; + st_PortInfo.bUsed = false; + + stl_ListPort.push_back(st_PortInfo); + } + return true; +} +/******************************************************************** +函数名称:APIHelp_Port_Destroy +函数功能:销毁端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CAPIHelp_Port::APIHelp_Port_Destroy() +{ + APIHelp_IsErrorOccur = false; + + stl_ListPort.clear(); + return true; +} +/******************************************************************** +函数名称:APIHelp_Port_Get +函数功能:获取一个没有使用的端口 + 参数.一:pInt_Port + In/Out:Out + 类型:整数型指针 + 可空:N + 意思:输出获取到的端口 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CAPIHelp_Port::APIHelp_Port_Get(int* pInt_Port) +{ + APIHelp_IsErrorOccur = false; + + if (NULL == pInt_Port) + { + APIHelp_IsErrorOccur = true; + APIHelp_dwErrorCode = ERROR_STORAGE_MODULE_APIHELP_PARAMENT; + return false; + } + std::lock_guard st_Lock(st_Locker); + for (auto stl_ListIterator = stl_ListPort.begin(); stl_ListIterator != stl_ListPort.end(); stl_ListIterator++) + { + if (!stl_ListIterator->bUsed) + { + stl_ListIterator->bUsed = true; + *pInt_Port = stl_ListIterator->nPort; + break; + } + } + return true; +} +/******************************************************************** +函数名称:APIHelp_Port_Free +函数功能:还原一个端口 + 参数.一:nPort + In/Out:In + 类型:整数型 + 可空:N + 意思:输入要还原的端口号 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CAPIHelp_Port::APIHelp_Port_Free(int nPort) +{ + APIHelp_IsErrorOccur = false; + + std::lock_guard st_Lock(st_Locker); + for (auto stl_ListIterator = stl_ListPort.begin(); stl_ListIterator != stl_ListPort.end(); stl_ListIterator++) + { + if (nPort == stl_ListIterator->nPort) + { + stl_ListIterator->bUsed = false; + } + } + return true; +} \ No newline at end of file diff --git a/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.h b/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.h new file mode 100644 index 00000000..924e9d52 --- /dev/null +++ b/XEngine_Source/StorageModule_APIHelp/APIHelp_Port/APIHelp_Port.h @@ -0,0 +1,34 @@ +#pragma once +/******************************************************************** +// Created: 2026/09/11 14:46:00 +// File Name: D:\XEngine_Storage\XEngine_Source\StorageModule_APIHelp\APIHelp_Port\APIHelp_Port.h +// File Path: D:\XEngine_Storage\XEngine_Source\StorageModule_APIHelp\APIHelp_Port +// File Base: APIHelp_Port +// File Ext: h +// Project: XEngine_Storage +// Repository: D:\XEngine_Storage (branch: develop) +// Author: qyt +// Purpose: 端口处理类 +// History: +*********************************************************************/ +typedef struct +{ + int nPort; + bool bUsed; +}MODULEHELP_PORTINFO; + +class CAPIHelp_Port +{ +public: + CAPIHelp_Port(); + ~CAPIHelp_Port(); +public: + bool APIHelp_Port_Init(int nPortStart = 54000, int nPortEnd = 55000); + bool APIHelp_Port_Destroy(); + bool APIHelp_Port_Get(int* pInt_Port); + bool APIHelp_Port_Free(int nPort); +private: + std::mutex st_Locker; +private: + std::list stl_ListPort; +}; \ No newline at end of file diff --git a/XEngine_Source/StorageModule_APIHelp/Makefile b/XEngine_Source/StorageModule_APIHelp/Makefile index b878bcde..19ba2b1d 100644 --- a/XEngine_Source/StorageModule_APIHelp/Makefile +++ b/XEngine_Source/StorageModule_APIHelp/Makefile @@ -6,7 +6,7 @@ LOADHDR = -I ./ -I ../XEngine_DependLibrary/XEngine_Module/jsoncpp LOADSO = -L ../XEngine_DependLibrary/XEngine_Module/jsoncpp LIB = -lXEngine_BaseLib -lXEngine_Cryption -lRfcComponents_HttpProtocol -lXEngine_SystemApi -ljsoncpp LIBEX = -OBJECTS = APIHelp_Distributed.o APIHelp_Api.o pch.o +OBJECTS = APIHelp_Distributed.o APIHelp_Api.o APIHelp_Port.o pch.o ifeq ($(RELEASE),1) FLAGS = -c -D _RELEASE @@ -55,6 +55,8 @@ APIHelp_Api.o:./APIHelp_Api/APIHelp_Api.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./APIHelp_Api/APIHelp_Api.cpp APIHelp_Cryption.o:./APIHelp_Cryption/APIHelp_Cryption.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./APIHelp_Cryption/APIHelp_Cryption.cpp +APIHelp_Port.o:./APIHelp_Port/APIHelp_Port.cpp + $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./APIHelp_Port/APIHelp_Port.cpp pch.o:./pch.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./pch.cpp diff --git a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.def b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.def index 7d462662..ec8e2ec6 100644 --- a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.def +++ b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.def @@ -21,4 +21,9 @@ EXPORTS APIHelp_Api_UrlStr APIHelp_Api_GetLastName APIHelp_Api_WDToUrl - APIHelp_Api_BSizeToStr \ No newline at end of file + APIHelp_Api_BSizeToStr + + APIHelp_Port_Init + APIHelp_Port_Destroy + APIHelp_Port_Get + APIHelp_Port_Free \ No newline at end of file diff --git a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj index d6a482e8..3aa5e46d 100644 --- a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj +++ b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj @@ -250,12 +250,14 @@ + + Create diff --git a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj.filters b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj.filters index 14ad6f8d..29ecf974 100644 --- a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj.filters +++ b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj.filters @@ -25,6 +25,12 @@ {70c91174-907f-422b-8db6-f8ee8298447c} + + {1331dd62-d795-488f-b5d4-f19a55b301b3} + + + {ec0c3ded-f887-448b-b9de-8ea580cefcbe} + @@ -45,6 +51,9 @@ 头文件\APIHelp_Api + + 头文件\APIHelp_Port + @@ -59,6 +68,9 @@ 源文件\APIHelp_Api + + 源文件\APIHelp_Port + diff --git a/XEngine_Source/StorageModule_APIHelp/pch.cpp b/XEngine_Source/StorageModule_APIHelp/pch.cpp index 3e3fc05c..f5fc023b 100644 --- a/XEngine_Source/StorageModule_APIHelp/pch.cpp +++ b/XEngine_Source/StorageModule_APIHelp/pch.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "APIHelp_Distributed/APIHelp_Distributed.h" #include "APIHelp_Api/APIHelp_Api.h" +#include "APIHelp_Port/APIHelp_Port.h" /******************************************************************** // Created: 2021/07/08 16:40:04 // File Name: D:\XEngine_Storage\XEngine_Source\StorageModule_APIHelp\pch.cpp @@ -17,6 +18,7 @@ XLONG APIHelp_dwErrorCode = 0; ////////////////////////////////////////////////////////////////////////// CAPIHelp_Distributed m_APIDistributed; CAPIHelp_Api m_APIHelp; +CAPIHelp_Port m_APIPort; ////////////////////////////////////////////////////////////////////////// // 导出函数 ////////////////////////////////////////////////////////////////////////// @@ -105,4 +107,23 @@ extern "C" bool APIHelp_Api_WDToUrl(LPCXSTR lpszUrl, XCHAR* ptszUrl) extern "C" bool APIHelp_Api_BSizeToStr(__int64x nSize, XCHAR* ptszSizeStr, int nType) { return m_APIHelp.APIHelp_Api_BSizeToStr(nSize, ptszSizeStr, nType); +} +/************************************************************************/ +/* 端口函数 */ +/************************************************************************/ +extern "C" bool APIHelp_Port_Init(int nPortStart, int nPortEnd) +{ + return m_APIPort.APIHelp_Port_Init(nPortStart, nPortEnd); +} +extern "C" bool APIHelp_Port_Destroy() +{ + return m_APIPort.APIHelp_Port_Destroy(); +} +extern "C" bool APIHelp_Port_Get(int* pInt_Port) +{ + return m_APIPort.APIHelp_Port_Get(pInt_Port); +} +extern "C" bool APIHelp_Port_Free(int nPort) +{ + return m_APIPort.APIHelp_Port_Free(nPort); } \ No newline at end of file diff --git a/XEngine_Source/StorageModule_APIHelp/pch.h b/XEngine_Source/StorageModule_APIHelp/pch.h index 212e0e7e..4da4d077 100644 --- a/XEngine_Source/StorageModule_APIHelp/pch.h +++ b/XEngine_Source/StorageModule_APIHelp/pch.h @@ -22,6 +22,7 @@ #include #include #include +#include #include using namespace std; #include diff --git a/XEngine_Source/StorageModule_Config/Config_Define.h b/XEngine_Source/StorageModule_Config/Config_Define.h index d10690a4..c66a8826 100644 --- a/XEngine_Source/StorageModule_Config/Config_Define.h +++ b/XEngine_Source/StorageModule_Config/Config_Define.h @@ -21,7 +21,8 @@ typedef struct tag_XEngine_ServerConfig int nCenterPort; int nWebdavPort; int nFTPCPort; - int nFTPDPort; + int nFTPDStart; + int nFTPDEnd; int nStorageDLPort; int nStorageUPPort; struct diff --git a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp index b142b8c1..d36d4c59 100644 --- a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp +++ b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp @@ -88,8 +88,8 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->nWebdavPort = st_JsonRoot["nWebdavPort"].asInt(); pSt_ServerConfig->nStorageDLPort = st_JsonRoot["nStorageDLPort"].asInt(); pSt_ServerConfig->nStorageUPPort = st_JsonRoot["nStorageUPPort"].asInt(); - pSt_ServerConfig->nFTPCPort = st_JsonRoot["nFTPCPort"].asInt(); - pSt_ServerConfig->nFTPDPort = st_JsonRoot["nFTPDPort"].asInt(); + pSt_ServerConfig->nFTPDStart = st_JsonRoot["nFTPDStart"].asInt(); + pSt_ServerConfig->nFTPDEnd = st_JsonRoot["nFTPDEnd"].asInt(); if (st_JsonRoot["XMax"].empty() || (8 != st_JsonRoot["XMax"].size())) { diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index d8100f64..c93f6246 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -31,37 +31,6 @@ XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam) } return 0; } -XHTHREAD XCALLBACK XEngine_FTPDatas_Thread(XPVOID lParam) -{ - int nThreadPos = *(int*)lParam; - nThreadPos++; - - while (bIsRun) - { - //等待指定线程事件触发 - if (FTPProtocol_Parse_EventWaitEx(xhFTPDatas, nThreadPos)) - { - int nListCount = 0; - XENGINE_MANAGEPOOL_TASKEVENT** ppSt_PKTClient; - //获取当前队列池中所有触发上传客户端 - FTPProtocol_Parse_GetPoolEx(xhFTPDatas, nThreadPos, &ppSt_PKTClient, &nListCount); - for (int i = 0; i < nListCount; i++) - { - for (int j = 0; j < ppSt_PKTClient[i]->nPktCount; j++) - { - XENGINE_KEYVALUE st_KeyValue = {}; - //获得指定上传客户端触发信息 - if (FTPProtocol_Parse_GetClientEx(xhFTPDatas, ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue)) - { - XEngine_Task_FTP(ppSt_PKTClient[i]->tszClientAddr, &st_KeyValue, STORAGE_NETTYPE_FTPDATAS); - } - } - } - BaseLib_Memory_Free((XPPPMEM)&ppSt_PKTClient, nListCount); - } - } - return 0; -} bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, int nNetType) { @@ -95,19 +64,37 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in { //被动模式请求 int nPort = 0; - XCHAR tszIPAddr[XPATH_MIN] = {}; APIADDR_IPADDR st_IPAddr = {}; APIAddr_IPAddr_IsIPV4Addr(st_ServiceCfg.tszIPAddr, &st_IPAddr); FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, &nRVLen, false); - - int p1 = st_ServiceCfg.nFTPDPort / 256; - int p2 = st_ServiceCfg.nFTPDPort % 256; + if (!APIHelp_Port_Get(&nPort)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_425, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,资源耗尽,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + int p1 = nPort / 256; + int p2 = nPort % 256; _xstprintf(tszSDBuffer, _X("%d %s (%d,%d,%d,%d,%d,%d)"), XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, st_IPAddr.nIPAddr1, st_IPAddr.nIPAddr2, st_IPAddr.nIPAddr3, st_IPAddr.nIPAddr4, p1, p2); + if (!NetCore_TCPSelect_StartEx(nPort, 100, true)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_425, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,资源耗尽,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求被动模式成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETR))) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求文件成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } else { FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h index 4926241c..5046372d 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.h @@ -11,5 +11,4 @@ // History: *********************************************************************/ XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam); -XHTHREAD XCALLBACK XEngine_FTPDatas_Thread(XPVOID lParam); bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE* pSt_KeyValue, int nNetType); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h index 9f312795..73fc6925 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h @@ -93,21 +93,18 @@ extern XHANDLE xhHBUPLoader; extern XHANDLE xhHBCenter; extern XHANDLE xhHBWebdav; extern XHANDLE xhHBFTPContral; -extern XHANDLE xhHBFTPDatas; extern XHANDLE xhNetDownload; extern XHANDLE xhNetUPLoader; extern XHANDLE xhNetCenter; extern XHANDLE xhNetWebdav; extern XHANDLE xhNetFTPContral; -extern XHANDLE xhNetFTPDatas; extern XHANDLE xhUPPool; extern XHANDLE xhDLPool; extern XHANDLE xhCTPool; extern XHANDLE xhWDPool; extern XHANDLE xhFTPPoolContral; -extern XHANDLE xhFTPPoolDatas; extern XHANDLE xhDLSsl; extern XHANDLE xhUPSsl; @@ -120,7 +117,6 @@ extern XHANDLE xhDLHttp; extern XHANDLE xhCenterHttp; extern XHANDLE xhWebdavHttp; extern XHANDLE xhFTPContral; -extern XHANDLE xhFTPDatas; extern XSOCKET hBroadSocket; extern shared_ptr pSTDThread; diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp index 6a7d0179..c8eaf5f1 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp @@ -9,21 +9,18 @@ XHANDLE xhHBUPLoader = NULL; XHANDLE xhHBCenter = NULL; XHANDLE xhHBWebdav = NULL; XHANDLE xhHBFTPContral = NULL; -XHANDLE xhHBFTPDatas = NULL; XHANDLE xhNetDownload = NULL; XHANDLE xhNetUPLoader = NULL; XHANDLE xhNetCenter = NULL; XHANDLE xhNetWebdav = NULL; XHANDLE xhNetFTPContral = NULL; -XHANDLE xhNetFTPDatas = NULL; XHANDLE xhUPPool = NULL; XHANDLE xhDLPool = NULL; XHANDLE xhCTPool = NULL; XHANDLE xhWDPool = NULL; XHANDLE xhFTPPoolContral = NULL; -XHANDLE xhFTPPoolDatas = NULL; XHANDLE xhDLSsl = NULL; XHANDLE xhUPSsl = NULL; @@ -36,7 +33,6 @@ XHANDLE xhDLHttp = NULL; XHANDLE xhCenterHttp = NULL; XHANDLE xhWebdavHttp = NULL; XHANDLE xhFTPContral = NULL; -XHANDLE xhFTPDatas = NULL; XSOCKET hBroadSocket = 0; shared_ptr pSTDThread = NULL; @@ -57,7 +53,6 @@ void ServiceApp_Stop(int signo) HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); FTPProtocol_Parse_DestroyEx(xhFTPContral); - FTPProtocol_Parse_DestroyEx(xhFTPDatas); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); @@ -69,21 +64,18 @@ void ServiceApp_Stop(int signo) NetCore_TCPXCore_DestroyEx(xhNetCenter); NetCore_TCPXCore_DestroyEx(xhNetWebdav); NetCore_TCPXCore_DestroyEx(xhNetFTPContral); - NetCore_TCPXCore_DestroyEx(xhNetFTPDatas); SocketOpt_HeartBeat_DestoryEx(xhHBDownload); SocketOpt_HeartBeat_DestoryEx(xhHBUPLoader); SocketOpt_HeartBeat_DestoryEx(xhHBCenter); SocketOpt_HeartBeat_DestoryEx(xhHBWebdav); SocketOpt_HeartBeat_DestoryEx(xhHBFTPContral); - SocketOpt_HeartBeat_DestoryEx(xhHBFTPDatas); ManagePool_Thread_NQDestroy(xhUPPool); ManagePool_Thread_NQDestroy(xhDLPool); ManagePool_Thread_NQDestroy(xhCTPool); ManagePool_Thread_NQDestroy(xhWDPool); ManagePool_Thread_NQDestroy(xhFTPPoolContral); - ManagePool_Thread_NQDestroy(xhFTPPoolDatas); Algorithm_Calculation_Close(xhLimit); HelpComponents_XLog_Destroy(xhLog); @@ -92,6 +84,7 @@ void ServiceApp_Stop(int signo) Session_UPStroage_Destory(); Database_File_Destory(); Database_Memory_Destory(); + APIHelp_Port_Destroy(); if (NULL != pSTDThread) { @@ -197,7 +190,6 @@ int main(int argc, char** argv) THREADPOOL_PARAMENT** ppSt_ListCTThread; THREADPOOL_PARAMENT** ppSt_ListWDThread; THREADPOOL_PARAMENT** ppSt_ListFTPCThread; - THREADPOOL_PARAMENT** ppSt_ListFTPDThread; memset(&st_XLogConfig, '\0', sizeof(HELPCOMPONENTS_XLOG_CONFIGURE)); memset(&st_ServiceCfg, '\0', sizeof(XENGINE_SERVERCONFIG)); @@ -596,41 +588,8 @@ int main(int argc, char** argv) goto XENGINE_EXITAPP; } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); - //数据端口启用 - xhFTPDatas = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); - if (NULL == xhFTPDatas) - { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,初始化FTP服务失败,错误:%lX"), FTPProtocol_GetLastError()); - goto XENGINE_EXITAPP; - } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP服务成功,IO线程个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); - xhNetFTPDatas = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPDPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); - if (NULL == xhNetFTPDatas) - { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPDPort, NetCore_GetLastError()); - goto XENGINE_EXITAPP; - } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPDPort, st_ServiceCfg.st_XMax.nIOThread); - NetCore_TCPXCore_RegisterCallBackEx(xhNetFTPDatas, XEngine_Callback_FTPDatasLogin, XEngine_Callback_FTPDatasRecv, XEngine_Callback_FTPDatasLeave); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,注册FTP网络服务事件成功!")); - - BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListFTPDThread, st_ServiceCfg.st_XMax.nFTPThread, sizeof(THREADPOOL_PARAMENT)); - for (int i = 0; i < st_ServiceCfg.st_XMax.nFTPThread; i++) - { - int* pInt_Pos = new int; - *pInt_Pos = i; - - ppSt_ListFTPDThread[i]->lParam = pInt_Pos; - ppSt_ListFTPDThread[i]->fpCall_ThreadsTask = XEngine_FTPDatas_Thread; - } - xhFTPPoolDatas = ManagePool_Thread_NQCreate(&ppSt_ListFTPDThread, st_ServiceCfg.st_XMax.nFTPThread); - if (NULL == xhFTPPoolDatas) - { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP处理线程池失败,错误:%d"), errno); - goto XENGINE_EXITAPP; - } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); + APIHelp_Port_Init(st_ServiceCfg.nFTPDStart, st_ServiceCfg.nFTPDEnd); } //只有使用了数据库,才启用P2P if (st_ServiceCfg.st_P2xp.bEnable) @@ -739,7 +698,6 @@ int main(int argc, char** argv) HttpProtocol_Server_DestroyEx(xhCenterHttp); HttpProtocol_Server_DestroyEx(xhWebdavHttp); FTPProtocol_Parse_DestroyEx(xhFTPContral); - FTPProtocol_Parse_DestroyEx(xhFTPDatas); Cryption_Server_StopEx(xhDLSsl); Cryption_Server_StopEx(xhUPSsl); @@ -751,21 +709,18 @@ int main(int argc, char** argv) NetCore_TCPXCore_DestroyEx(xhNetCenter); NetCore_TCPXCore_DestroyEx(xhNetWebdav); NetCore_TCPXCore_DestroyEx(xhNetFTPContral); - NetCore_TCPXCore_DestroyEx(xhNetFTPDatas); SocketOpt_HeartBeat_DestoryEx(xhHBDownload); SocketOpt_HeartBeat_DestoryEx(xhHBUPLoader); SocketOpt_HeartBeat_DestoryEx(xhHBCenter); SocketOpt_HeartBeat_DestoryEx(xhHBWebdav); SocketOpt_HeartBeat_DestoryEx(xhHBFTPContral); - SocketOpt_HeartBeat_DestoryEx(xhHBFTPDatas); ManagePool_Thread_NQDestroy(xhUPPool); ManagePool_Thread_NQDestroy(xhDLPool); ManagePool_Thread_NQDestroy(xhCTPool); ManagePool_Thread_NQDestroy(xhWDPool); ManagePool_Thread_NQDestroy(xhFTPPoolContral); - ManagePool_Thread_NQDestroy(xhFTPPoolDatas); Algorithm_Calculation_Close(xhLimit); HelpComponents_XLog_Destroy(xhLog); @@ -774,6 +729,7 @@ int main(int argc, char** argv) Session_UPStroage_Destory(); Database_File_Destory(); Database_Memory_Destory(); + APIHelp_Port_Destroy(); if (NULL != pSTDThread) { From 47392e75642660fce9eb4a7194205ac5a60ba72c Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sat, 12 Sep 2026 14:37:55 +0800 Subject: [PATCH 04/33] added:list data transfer for ftp --- .../Config_Json/Config_Json.cpp | 1 + .../StorageModule_Session/Session_Define.h | 129 ++++++++++- .../StorageModule_Session.def | 9 +- .../StorageModule_Session.vcxproj | 2 + .../StorageModule_Session.vcxproj.filters | 12 + XEngine_Source/StorageModule_Session/pch.cpp | 29 +++ .../XEngine_StorageApp/StorageApp_FTP.cpp | 210 +++++++++++++++++- .../XEngine_StorageApp/StorageApp_Hdr.h | 2 +- .../XEngine_StorageApp/StorageApp_Network.cpp | 56 +---- .../XEngine_StorageApp/StorageApp_Network.h | 5 - .../XEngine_StorageApp/XEngine_StorageApp.cpp | 1 + 11 files changed, 386 insertions(+), 70 deletions(-) diff --git a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp index d36d4c59..c01c61c3 100644 --- a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp +++ b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp @@ -88,6 +88,7 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->nWebdavPort = st_JsonRoot["nWebdavPort"].asInt(); pSt_ServerConfig->nStorageDLPort = st_JsonRoot["nStorageDLPort"].asInt(); pSt_ServerConfig->nStorageUPPort = st_JsonRoot["nStorageUPPort"].asInt(); + pSt_ServerConfig->nFTPCPort = st_JsonRoot["nFTPCPort"].asInt(); pSt_ServerConfig->nFTPDStart = st_JsonRoot["nFTPDStart"].asInt(); pSt_ServerConfig->nFTPDEnd = st_JsonRoot["nFTPDEnd"].asInt(); diff --git a/XEngine_Source/StorageModule_Session/Session_Define.h b/XEngine_Source/StorageModule_Session/Session_Define.h index d653feac..cde3fbaf 100644 --- a/XEngine_Source/StorageModule_Session/Session_Define.h +++ b/XEngine_Source/StorageModule_Session/Session_Define.h @@ -637,4 +637,131 @@ extern "C" bool Session_Webdav_Get(LPCXSTR lpszFileName, XENGINE_WEBDAVLOCK* pSt 意思:是否成功 备注: *********************************************************************/ -extern "C" bool Session_Webdav_Delete(LPCXSTR lpszFileName); \ No newline at end of file +extern "C" bool Session_Webdav_Delete(LPCXSTR lpszFileName); +/************************************************************************/ +/* FTP会话导出的函数 */ +/************************************************************************/ +/******************************************************************** +函数名称:Session_FTP_Insert +函数功能:插入一个会话 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入客户端地址 + 参数.二:bUPLoader + In/Out:In + 类型:逻辑型 + 可空:N + 意思:输入是否为上传会话 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader); +/******************************************************************** +函数名称:Session_FTP_Set +函数功能:设置 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入客户端地址 + 参数.二:lpszFileName + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入要操作的文件 + 参数.三:lpszFilePath + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入要操作的文件路径 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName = NULL, LPCXSTR lpszFilePath = NULL); +/******************************************************************** +函数名称:Session_FTP_Get +函数功能:获取信息 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:ptszFileName + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的文件 + 参数.三:ptszFilePath + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的文件路径 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName = NULL, XCHAR* ptszFilePath = NULL); +/******************************************************************** +函数名称:Session_FTP_SetSocket +函数功能:设置网络句柄 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:nPort + In/Out:In + 类型:整数型 + 可空:N + 意思:输入操作的端口 + 参数.二:xhToken + In/Out:In + 类型:句柄 + 可空:N + 意思:输入操作的套接字句柄 +返回值 + 类型:逻辑型 + 意思:是否设置成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE xhToken); +/******************************************************************** +函数名称:Session_FTP_GetSocket +函数功能:获取网络句柄 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:pInt_Port + In/Out:Out + 类型:整数型指针 + 可空:Y + 意思:输出操作的端口 +返回值 + 类型:句柄 + 意思:返回获取到的网络句柄 +备注: +*********************************************************************/ +extern "C" XHANDLE Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port = NULL); +/******************************************************************** +函数名称:Session_FTP_Delete +函数功能:删除一个会话 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_Delete(LPCXSTR lpszClientAddr); \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Session/StorageModule_Session.def b/XEngine_Source/StorageModule_Session/StorageModule_Session.def index 5d442f0d..73607b80 100644 --- a/XEngine_Source/StorageModule_Session/StorageModule_Session.def +++ b/XEngine_Source/StorageModule_Session/StorageModule_Session.def @@ -36,4 +36,11 @@ EXPORTS Session_Webdav_Insert Session_Webdav_Get - Session_Webdav_Delete \ No newline at end of file + Session_Webdav_Delete + + Session_FTP_Insert + Session_FTP_Set + Session_FTP_Get + Session_FTP_SetSocket + Session_FTP_GetSocket + Session_FTP_Delete \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj index 80b07130..a1785334 100644 --- a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj +++ b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj @@ -249,6 +249,7 @@ + @@ -264,6 +265,7 @@ Create + diff --git a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj.filters b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj.filters index b9bc978e..3f11654d 100644 --- a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj.filters +++ b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj.filters @@ -31,6 +31,12 @@ {ada350db-0019-4645-b441-fe09e2d37c31} + + {525464d9-6fe5-4f5a-acf5-61f0d9219561} + + + {ae4fe5fb-dab3-41d8-a51d-106684a4587c} + @@ -57,6 +63,9 @@ 头文件\Session_Webdav + + 头文件\Session_FTPContral + @@ -77,6 +86,9 @@ 源文件\Session_Webdav + + 源文件\Session_FTPContral + diff --git a/XEngine_Source/StorageModule_Session/pch.cpp b/XEngine_Source/StorageModule_Session/pch.cpp index e9eb29c4..dea10160 100644 --- a/XEngine_Source/StorageModule_Session/pch.cpp +++ b/XEngine_Source/StorageModule_Session/pch.cpp @@ -3,6 +3,7 @@ #include "Session_Stroage/Session_UPStroage.h" #include "Session_Action/Session_Action.h" #include "Session_Webdav/Session_Webdav.h" +#include "Session_FTPContral/Session_FTPContral.h" /******************************************************************** // Created: 2021/06/02 14:38:34 // File Name: D:\XEngine_Storage\StorageModule_Session\pch.cpp @@ -21,6 +22,7 @@ CSession_DLStroage m_DLStorage; CSession_UPStroage m_UPStorage; CSession_Action m_Action; CSession_Webdav m_Webdav; +CSession_FTPContral m_FTPContral; ////////////////////////////////////////////////////////////////////////// // 导出函数定义机器 ////////////////////////////////////////////////////////////////////////// @@ -164,4 +166,31 @@ extern "C" bool Session_Webdav_Get(LPCXSTR lpszFileName, XENGINE_WEBDAVLOCK* pSt extern "C" bool Session_Webdav_Delete(LPCXSTR lpszFileName) { return m_Webdav.Session_Webdav_Delete(lpszFileName); +} +/************************************************************************/ +/* FTP会话导出的函数 */ +/************************************************************************/ +extern "C" bool Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader) +{ + return m_FTPContral.Session_FTP_Insert(lpszClientAddr, bUPLoader); +} +extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName, LPCXSTR lpszFilePath) +{ + return m_FTPContral.Session_FTP_Set(lpszClientAddr, lpszFileName, lpszFilePath); +} +extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName, XCHAR* ptszFilePath) +{ + return m_FTPContral.Session_FTP_Get(lpszClientAddr, ptszFileName, ptszFilePath); +} +extern "C" bool Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE xhToken) +{ + return m_FTPContral.Session_FTP_SetSocket(lpszClientAddr, nPort, xhToken); +} +extern "C" XHANDLE Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port) +{ + return m_FTPContral.Session_FTP_GetSocket(lpszClientAddr, pInt_Port); +} +extern "C" bool Session_FTP_Delete(LPCXSTR lpszClientAddr) +{ + return m_FTPContral.Session_FTP_Delete(lpszClientAddr); } \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index c93f6246..83b771a3 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -31,13 +31,128 @@ XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam) } return 0; } +static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName) +{ + XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr); + if (NULL == xhToken) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr, Session_GetLastError()); + return; + } + FILE* pSt_File = _xtfopen(lpszFileName, _X("rb")); + if (NULL == pSt_File) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,打开文件:%s失败"), lpszClientAddr, lpszFileName); + return; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备数据文件:%s 完成,开始发送数据"), lpszClientAddr, lpszFileName); + + while (true) + { + XCHAR tszMSGBuffer[XPATH_2MAX] = {}; + int nRet = fread(tszMSGBuffer, 1, sizeof(tszMSGBuffer), pSt_File); + if (nRet <= 0) + { + break; + } + while (true) + { + if (NetCore_TCPSelect_SendEx(xhToken, lpszClientAddr, tszMSGBuffer, nRet)) + { + break; + } + else + { + if (ERROR_XENGINE_BASELIB_BASELIB_IO_CLOSE == NetCore_GetLastError()) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,网络连接已断开"), lpszClientAddr); + break; + } + } + //等待10毫秒 + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + fclose(pSt_File); + //关闭连接 + NetCore_TCPSelect_StopEx(xhToken); + //通知 + int nSDLen = 1024; + XCHAR tszSDBuffer[1024] = {}; + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,文件发送数据完毕,网络连接已断开"), lpszClientAddr); +} +static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath) +{ + int nPort = 0; + XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); + if (NULL == xhToken) + { + //关闭连接 + APIHelp_Port_Free(nPort); //还原端口 + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); + return; + } + int nMSGLen = 0; + XLONG dwEvent = XENGINE_NETCORE_TCP_SELECT_EVENT_LOGIN; + XCHAR tszClientAddr[XPATH_MIN] = {}; + XCHAR tszMSGBuffer[XPATH_8MAX] = {}; + if (!NetCore_TCPSelect_ReadIOEventEx(xhToken, tszClientAddr, tszMSGBuffer, &nMSGLen, &dwEvent)) + { + //关闭连接 + NetCore_TCPSelect_StopEx(xhToken); + APIHelp_Port_Free(nPort); //还原端口 + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); + return; + } + + XCHAR tszCMDBuffer[XPATH_2MAX] = {}; + _xstprintf(tszCMDBuffer, _X("dir %s"), lpszFilePath.c_str()); + SystemApi_Process_ReadCmdReturn(tszCMDBuffer, tszMSGBuffer, 0, 6, &nMSGLen, 3); + /* + int nListCount = 0; + XCHAR** pptszFileList; + SystemApi_File_EnumFileA(lpszFilePath, &pptszFileList, &nListCount, false); + for (int i = 0; i < nListCount; i++) + { + XCHAR tszFileName[XPATH_MAX] = {}; + XCHAR tszFileAttr[XPATH_2MAX] = {}; + SYSTEMAPI_FILE_ATTR st_FileAttr = {}; + SystemApi_File_GetFileAttr(pptszFileList[i], &st_FileAttr); + + BaseLib_String_GetFileAndPath(pptszFileList[i], NULL, tszFileName); + if (st_FileAttr.bFile) + { + + } + _xsntprintf(tszFileAttr, XPATH_2MAX, _X("%s 1 ftp ftp %lld %s %s "), pptszFileList[i], st_FileAttr.nFileSize, st_FileAttr.tszFileTime, st_FileAttr.tszFileDate); + } + */ + if (!NetCore_TCPSelect_SendEx(xhToken, tszClientAddr, tszMSGBuffer, nMSGLen)) + { + //关闭连接 + NetCore_TCPSelect_StopEx(xhToken); + APIHelp_Port_Free(nPort); //还原端口 + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,连接地址:%s,发送数据大小:%d 失败,错误码:%lX"), lpszClientAddr.c_str(), tszClientAddr, nMSGLen, NetCore_GetLastError()); + } + //关闭连接 + NetCore_TCPSelect_StopEx(xhToken); + APIHelp_Port_Free(nPort); //还原端口 + //通知 + int nSDLen = 1024; + XCHAR tszSDBuffer[1024] = {}; + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s,枚举发送数据完毕,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr); +} bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, int nNetType) { - int nSDLen = 8192; - int nRVLen = 8192; - XCHAR tszSDBuffer[8192] = {}; - XCHAR tszRVBuffer[8192] = {}; + int nSDLen = 1024; + int nRVLen = 1024; + XCHAR tszSDBuffer[1024] = {}; + XCHAR tszRVBuffer[1024] = {}; if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_USER, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_USER))) { @@ -49,6 +164,8 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PASS, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PASS))) { //密码请求 + Session_FTP_Insert(lpszClientAddr, false); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_230, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求密码成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); @@ -75,29 +192,106 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,资源耗尽,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); return false; } + int p1 = nPort / 256; int p2 = nPort % 256; - _xstprintf(tszSDBuffer, _X("%d %s (%d,%d,%d,%d,%d,%d)"), XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, st_IPAddr.nIPAddr1, st_IPAddr.nIPAddr2, st_IPAddr.nIPAddr3, st_IPAddr.nIPAddr4, p1, p2); + nSDLen = _xstprintf(tszSDBuffer, _X("%d %s (%d,%d,%d,%d,%d,%d)"), XENGINE_FTPROTOCOL_RESPONSE_227, tszRVBuffer, st_IPAddr.nIPAddr1, st_IPAddr.nIPAddr2, st_IPAddr.nIPAddr3, st_IPAddr.nIPAddr4, p1, p2); - if (!NetCore_TCPSelect_StartEx(nPort, 100, true)) + XHANDLE xhToken = NetCore_TCPSelect_StartEx(nPort, 100, true); + if (NULL == xhToken) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_425, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,资源耗尽,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); return false; } + Session_FTP_SetSocket(lpszClientAddr, nPort, xhToken); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求被动模式成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求被动模式成功,请求地址:%s:%d"), lpszClientAddr, st_ServiceCfg.tszIPAddr, nPort); } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETR))) { + if (0 != _xtaccess(pSt_KeyValue->tszStrVlu, 0)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_550, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求文件:%s 失败,文件不存在"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + if (!Session_FTP_Set(lpszClientAddr, pSt_KeyValue->tszStrVlu)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求文件:%s 失败,没有设置服务器端口信息"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + + std::thread m_ThreadFTPDatas(XEngine_FTPDatas_Thread, lpszClientAddr, pSt_KeyValue->tszStrVlu); + m_ThreadFTPDatas.detach(); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求文件成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,退出成功"), lpszClientAddr); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PWD))) + { +#ifdef _MSC_BUILD + _getcwd(tszRVBuffer, nRVLen); +#else + getcwd(tszRVBuffer, nRVLen); +#endif + nSDLen = _xstprintf(tszSDBuffer, _X("257 \"%s\" is current directory.\r\n"), tszRVBuffer); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,查询当前目录:%s 成功"), lpszClientAddr, tszRVBuffer); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CWD))) + { + nSDLen = _xstprintf(tszSDBuffer, _X("250 Directory changed to %s\r\n"), pSt_KeyValue->tszStrVlu); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + Session_FTP_Set(lpszClientAddr, NULL, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CDUP, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CDUP))) + { + nSDLen = _xstprintf(tszSDBuffer, _X("200 Directory changed to %s\r\n"), pSt_KeyValue->tszStrVlu); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + Session_FTP_Set(lpszClientAddr, NULL, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_LIST, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_LIST))) + { + //列举目录请求 + XCHAR tszFilePath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求列举目录:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + if (_tcsxlen(tszFilePath) <= 0) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求列举目录:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + + std::thread m_ThreadFTPFiles(XEngine_FTPFile_Thread, xstring(lpszClientAddr), xstring(tszFilePath)); + m_ThreadFTPFiles.detach(); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,列举目录:%s 成功"), lpszClientAddr, tszFilePath); + } else { - FTPProtocol_Packet_Request(tszSDBuffer, &nSDLen, pSt_KeyValue); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_502, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,发送的方法 %s:%s 不支持"), lpszClientAddr, pSt_KeyValue->tszStrKey, pSt_KeyValue->tszStrVlu); return false; diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h index 73fc6925..9906d462 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #else @@ -82,7 +83,6 @@ using namespace std; #define STORAGE_NETTYPE_HTTPCENTER 3 #define STORAGE_NETTYPE_HTTPWEBDAV 4 #define STORAGE_NETTYPE_FTPCONTRAL 5 -#define STORAGE_NETTYPE_FTPDATAS 6 extern bool bIsRun; extern bool bIsTest; diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp index 38d7fa2b..ef6c5a8f 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp @@ -187,33 +187,12 @@ void XCALLBACK XEngine_Callback_FTPContralRecv(LPCXSTR lpszClientAddr, XSOCKET h return; } SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPContral, lpszClientAddr); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_DEBUG, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); } void XCALLBACK XEngine_Callback_FTPContralLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) { XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTPCONTRAL); } -bool XCALLBACK XEngine_Callback_FTPDatasLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) -{ - FTPProtocol_Parse_CreateClientEx(xhFTPDatas, lpszClientAddr, 0); - SocketOpt_HeartBeat_InsertAddrEx(xhHBFTPDatas, lpszClientAddr); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,进入了服务器"), lpszClientAddr); - return true; -} -void XCALLBACK XEngine_Callback_FTPDatasRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam) -{ - if (!FTPProtocol_Parse_InsertQueueEx(xhFTPDatas, lpszClientAddr, lpszRecvMsg, nMsgLen)) - { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, FTPProtocol_GetLastError()); - return; - } - SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPDatas, lpszClientAddr); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,投递包成功,大小:%d"), lpszClientAddr, nMsgLen); -} -void XCALLBACK XEngine_Callback_FTPDatasLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam) -{ - XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_BYSELF, STORAGE_NETTYPE_FTPDATAS); -} ////////////////////////////////////////////////////////////////////////// void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) { @@ -235,10 +214,6 @@ void XCALLBACK XEngine_Callback_HBFTPContral(LPCXSTR lpszClientAddr, XSOCKET hSo { XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTPCONTRAL); } -void XCALLBACK XEngine_Callback_HBFTPDatas(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam) -{ - XEngine_Net_CloseClient(lpszClientAddr, STORAGE_LEAVETYPE_HEARTBEAT, STORAGE_NETTYPE_FTPDATAS); -} ////////////////////////////////////////////////////////////////////////// /* * 统一处理客户端离线后的资源清理逻辑。 @@ -386,26 +361,7 @@ bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClient SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPContral, lpszClientAddr); } FTPProtocol_Parse_DeleteClientEx(xhFTPContral, lpszClientAddr); - } - else if (STORAGE_NETTYPE_FTPDATAS == nClientType) - { - m_StrClient = _X("FTP数据客户端"); - if (STORAGE_LEAVETYPE_HEARTBEAT == nLeaveType) - { - m_StrLeaveMsg = _X("心跳超时"); - NetCore_TCPXCore_CloseForClientEx(xhNetFTPDatas, lpszClientAddr); - } - else if (STORAGE_LEAVETYPE_BYSELF == nLeaveType) - { - m_StrLeaveMsg = _X("被动断开"); - SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPDatas, lpszClientAddr); - } - else - { - m_StrLeaveMsg = _X("主动关闭"); - NetCore_TCPXCore_CloseForClientEx(xhNetFTPDatas, lpszClientAddr); - SocketOpt_HeartBeat_DeleteAddrEx(xhHBFTPDatas, lpszClientAddr); - } + Session_FTP_Delete(lpszClientAddr); } else { @@ -509,14 +465,6 @@ bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsg SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPContral, lpszClientAddr); } } - else if (STORAGE_NETTYPE_FTPDATAS == nType) - { - bRet = NetCore_TCPXCore_SendEx(xhNetFTPDatas, lpszClientAddr, lpszMsgBuffer, nMsgLen); - if (bRet && st_ServiceCfg.st_XTime.bHBTime) - { - SocketOpt_HeartBeat_ActiveAddrEx(xhHBFTPDatas, lpszClientAddr); - } - } if (!bRet) { XLONG dwRet = NetCore_GetLastError(); diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h index f0d95235..2854e23a 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.h @@ -20,16 +20,11 @@ bool XCALLBACK XEngine_Callback_FTPContralLogin(LPCXSTR lpszClientAddr, XSOCKET void XCALLBACK XEngine_Callback_FTPContralRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); void XCALLBACK XEngine_Callback_FTPContralLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); -bool XCALLBACK XEngine_Callback_FTPDatasLogin(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); -void XCALLBACK XEngine_Callback_FTPDatasRecv(LPCXSTR lpszClientAddr, XSOCKET hSocket, LPCXSTR lpszRecvMsg, int nMsgLen, XPVOID lParam); -void XCALLBACK XEngine_Callback_FTPDatasLeave(LPCXSTR lpszClientAddr, XSOCKET hSocket, XPVOID lParam); - void XCALLBACK XEngine_Callback_HBDownload(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBUPLoader(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBCenter(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBWebdav(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); void XCALLBACK XEngine_Callback_HBFTPContral(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); -void XCALLBACK XEngine_Callback_HBFTPDatas(LPCXSTR lpszClientAddr, XSOCKET hSocket, int nStatus, XPVOID lParam); bool XEngine_Net_CloseClient(LPCXSTR lpszClientAddr, int nLeaveType, int nClientType); bool XEngine_Net_SendMsg(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsgLen, int nType); \ No newline at end of file diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp index c8eaf5f1..5ae32d29 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp @@ -590,6 +590,7 @@ int main(int argc, char** argv) XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); APIHelp_Port_Init(st_ServiceCfg.nFTPDStart, st_ServiceCfg.nFTPDEnd); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP数据端口成功,端口范围:%d-%d"), st_ServiceCfg.nFTPDStart, st_ServiceCfg.nFTPDEnd); } //只有使用了数据库,才启用P2P if (st_ServiceCfg.st_P2xp.bEnable) From b11b8efd5b6e75e56d05159a3f2193fad91e46f9 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sat, 12 Sep 2026 17:08:15 +0800 Subject: [PATCH 05/33] modify:ftp list now support --- .../StorageModule_Config/Config_Define.h | 10 +- .../StorageModule_Config/Config_Error.h | 4 +- .../Config_Json/Config_Json.cpp | 19 +++- .../StorageModule_Session/Session_Define.h | 14 ++- XEngine_Source/StorageModule_Session/pch.cpp | 8 +- .../XEngine_StorageApp/StorageApp_FTP.cpp | 102 +++++++++++------- .../XEngine_StorageApp/XEngine_StorageApp.cpp | 12 +-- 7 files changed, 109 insertions(+), 60 deletions(-) diff --git a/XEngine_Source/StorageModule_Config/Config_Define.h b/XEngine_Source/StorageModule_Config/Config_Define.h index c66a8826..8f81405d 100644 --- a/XEngine_Source/StorageModule_Config/Config_Define.h +++ b/XEngine_Source/StorageModule_Config/Config_Define.h @@ -20,9 +20,7 @@ typedef struct tag_XEngine_ServerConfig bool bReuseraddr; int nCenterPort; int nWebdavPort; - int nFTPCPort; - int nFTPDStart; - int nFTPDEnd; + int nFTPPort; int nStorageDLPort; int nStorageUPPort; struct @@ -123,6 +121,12 @@ typedef struct tag_XEngine_ServerConfig int nRVMax; bool bEnable; }st_XAction; + struct + { + XCHAR tszFTPDir[XPATH_MID]; + int nFTPStart; + int nFTPEnd; + }st_XFtp; struct { list *pStl_ListStorage; diff --git a/XEngine_Source/StorageModule_Config/Config_Error.h b/XEngine_Source/StorageModule_Config/Config_Error.h index c7bdff06..63086716 100644 --- a/XEngine_Source/StorageModule_Config/Config_Error.h +++ b/XEngine_Source/StorageModule_Config/Config_Error.h @@ -24,7 +24,9 @@ #define ERROR_XENGINE_BLOGIC_CONFIG_JSON_XPROXY 0x0020009 #define ERROR_XENGINE_BLOGIC_CONFIG_JSON_P2XP 0x002000A #define ERROR_XENGINE_BLOGIC_CONFIG_JSON_CERT 0x002000B -#define ERROR_XENGINE_BLOGIC_CONFIG_JSON_CREPORT 0x002000D +#define ERROR_XENGINE_BLOGIC_CONFIG_JSON_REPORT 0x002000D +#define ERROR_XENGINE_BLOGIC_CONFIG_JSON_ACTION 0x002000E +#define ERROR_XENGINE_BLOGIC_CONFIG_JSON_FTP 0x002000F #define ERROR_XENGINE_BLOGIC_CONFIG_JSON_LBDISTRIBUTED 0x0020010 #define ERROR_XENGINE_BLOGIC_CONFIG_JSON_LBLOCATION 0x0020011 diff --git a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp index c01c61c3..87a27a5f 100644 --- a/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp +++ b/XEngine_Source/StorageModule_Config/Config_Json/Config_Json.cpp @@ -88,9 +88,7 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG pSt_ServerConfig->nWebdavPort = st_JsonRoot["nWebdavPort"].asInt(); pSt_ServerConfig->nStorageDLPort = st_JsonRoot["nStorageDLPort"].asInt(); pSt_ServerConfig->nStorageUPPort = st_JsonRoot["nStorageUPPort"].asInt(); - pSt_ServerConfig->nFTPCPort = st_JsonRoot["nFTPCPort"].asInt(); - pSt_ServerConfig->nFTPDStart = st_JsonRoot["nFTPDStart"].asInt(); - pSt_ServerConfig->nFTPDEnd = st_JsonRoot["nFTPDEnd"].asInt(); + pSt_ServerConfig->nFTPPort = st_JsonRoot["nFTPPort"].asInt(); if (st_JsonRoot["XMax"].empty() || (8 != st_JsonRoot["XMax"].size())) { @@ -233,7 +231,7 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG if (st_JsonRoot["XReport"].empty() || (3 != st_JsonRoot["XReport"].size())) { Config_IsErrorOccur = true; - Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_CREPORT; + Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_REPORT; return false; } Json::Value st_JsonReport = st_JsonRoot["XReport"]; @@ -244,13 +242,24 @@ bool CConfig_Json::Config_Json_File(LPCXSTR lpszConfigFile, XENGINE_SERVERCONFIG if (st_JsonRoot["XAction"].empty() || (3 != st_JsonRoot["XAction"].size())) { Config_IsErrorOccur = true; - Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_CREPORT; + Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_ACTION; return false; } Json::Value st_JsonXAction = st_JsonRoot["XAction"]; pSt_ServerConfig->st_XAction.bEnable = st_JsonXAction["bEnable"].asBool(); pSt_ServerConfig->st_XAction.nSDMax = st_JsonXAction["nSDMax"].asInt(); pSt_ServerConfig->st_XAction.nRVMax = st_JsonXAction["nRVMax"].asInt(); + + if (st_JsonRoot["XFtp"].empty() || (3 != st_JsonRoot["XFtp"].size())) + { + Config_IsErrorOccur = true; + Config_dwErrorCode = ERROR_XENGINE_BLOGIC_CONFIG_JSON_FTP; + return false; + } + Json::Value st_JsonXFtp = st_JsonRoot["XFtp"]; + pSt_ServerConfig->st_XFtp.nFTPStart = st_JsonXFtp["nFTPStart"].asInt(); + pSt_ServerConfig->st_XFtp.nFTPEnd = st_JsonXFtp["nFTPEnd"].asInt(); + _tcsxcpy(pSt_ServerConfig->st_XFtp.tszFTPDir, st_JsonXFtp["tszFTPDir"].asCString()); return true; } /******************************************************************** diff --git a/XEngine_Source/StorageModule_Session/Session_Define.h b/XEngine_Source/StorageModule_Session/Session_Define.h index cde3fbaf..ac2f1261 100644 --- a/XEngine_Source/StorageModule_Session/Session_Define.h +++ b/XEngine_Source/StorageModule_Session/Session_Define.h @@ -678,12 +678,17 @@ extern "C" bool Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader); 类型:常量字符指针 可空:Y 意思:输入要操作的文件路径 + 参数.四:lpszAlisName + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入路径别名 返回值 类型:逻辑型 意思:是否成功 备注: *********************************************************************/ -extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName = NULL, LPCXSTR lpszFilePath = NULL); +extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName = NULL, LPCXSTR lpszFilePath = NULL, LPCXSTR lpszAlisName = NULL); /******************************************************************** 函数名称:Session_FTP_Get 函数功能:获取信息 @@ -702,12 +707,17 @@ extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName = N 类型:字符指针 可空:Y 意思:输出操作的文件路径 + 参数.四:ptszAlisPath + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的路径别名 返回值 类型:逻辑型 意思:是否成功 备注: *********************************************************************/ -extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName = NULL, XCHAR* ptszFilePath = NULL); +extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName = NULL, XCHAR* ptszFilePath = NULL, XCHAR* ptszAlisPath = NULL); /******************************************************************** 函数名称:Session_FTP_SetSocket 函数功能:设置网络句柄 diff --git a/XEngine_Source/StorageModule_Session/pch.cpp b/XEngine_Source/StorageModule_Session/pch.cpp index dea10160..a25dd6b8 100644 --- a/XEngine_Source/StorageModule_Session/pch.cpp +++ b/XEngine_Source/StorageModule_Session/pch.cpp @@ -174,13 +174,13 @@ extern "C" bool Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader) { return m_FTPContral.Session_FTP_Insert(lpszClientAddr, bUPLoader); } -extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName, LPCXSTR lpszFilePath) +extern "C" bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName, LPCXSTR lpszFilePath, LPCXSTR lpszAlisName) { - return m_FTPContral.Session_FTP_Set(lpszClientAddr, lpszFileName, lpszFilePath); + return m_FTPContral.Session_FTP_Set(lpszClientAddr, lpszFileName, lpszFilePath, lpszAlisName); } -extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName, XCHAR* ptszFilePath) +extern "C" bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName, XCHAR* ptszFilePath, XCHAR* ptszAlisPath) { - return m_FTPContral.Session_FTP_Get(lpszClientAddr, ptszFileName, ptszFilePath); + return m_FTPContral.Session_FTP_Get(lpszClientAddr, ptszFileName, ptszFilePath, ptszAlisPath); } extern "C" bool Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE xhToken) { diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 83b771a3..226ca3a6 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -83,7 +83,7 @@ static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,文件发送数据完毕,网络连接已断开"), lpszClientAddr); } -static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath) +static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, xstring lpszAlisPath) { int nPort = 0; XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); @@ -106,29 +106,14 @@ static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath) XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); return; } - - XCHAR tszCMDBuffer[XPATH_2MAX] = {}; - _xstprintf(tszCMDBuffer, _X("dir %s"), lpszFilePath.c_str()); + XCHAR tszDIRBuffer[XPATH_MAX] = {}; + XCHAR tszCMDBuffer[XPATH_2MAX] = {}; + _xstprintf(tszDIRBuffer, _X("%s%s"), lpszFilePath.c_str(), lpszAlisPath.c_str()); + BaseLib_String_FixPath(tszDIRBuffer, 1); + + _xstprintf(tszCMDBuffer, _X("dir %s"), tszDIRBuffer); SystemApi_Process_ReadCmdReturn(tszCMDBuffer, tszMSGBuffer, 0, 6, &nMSGLen, 3); - /* - int nListCount = 0; - XCHAR** pptszFileList; - SystemApi_File_EnumFileA(lpszFilePath, &pptszFileList, &nListCount, false); - for (int i = 0; i < nListCount; i++) - { - XCHAR tszFileName[XPATH_MAX] = {}; - XCHAR tszFileAttr[XPATH_2MAX] = {}; - SYSTEMAPI_FILE_ATTR st_FileAttr = {}; - SystemApi_File_GetFileAttr(pptszFileList[i], &st_FileAttr); - BaseLib_String_GetFileAndPath(pptszFileList[i], NULL, tszFileName); - if (st_FileAttr.bFile) - { - - } - _xsntprintf(tszFileAttr, XPATH_2MAX, _X("%s 1 ftp ftp %lld %s %s "), pptszFileList[i], st_FileAttr.nFileSize, st_FileAttr.tszFileTime, st_FileAttr.tszFileDate); - } - */ if (!NetCore_TCPSelect_SendEx(xhToken, tszClientAddr, tszMSGBuffer, nMSGLen)) { //关闭连接 @@ -144,7 +129,7 @@ static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath) XCHAR tszSDBuffer[1024] = {}; FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s,枚举发送数据完毕,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s,枚举目录:%s 发送数据完毕,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr, tszDIRBuffer); } bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, int nNetType) @@ -165,6 +150,7 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in { //密码请求 Session_FTP_Insert(lpszClientAddr, false); + Session_FTP_Set(lpszClientAddr, NULL, st_ServiceCfg.st_XFtp.tszFTPDir); FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_230, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); @@ -241,41 +227,79 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PWD))) { -#ifdef _MSC_BUILD - _getcwd(tszRVBuffer, nRVLen); -#else - getcwd(tszRVBuffer, nRVLen); -#endif - nSDLen = _xstprintf(tszSDBuffer, _X("257 \"%s\" is current directory.\r\n"), tszRVBuffer); + XCHAR tszFTPDir[XPATH_MAX] = {}; + XCHAR tszAliDir[XPATH_MAX] = {}; + Session_FTP_Get(lpszClientAddr, NULL, tszFTPDir, tszAliDir); + + if (_tcsxlen(tszAliDir) > 0) + { + nSDLen = _xstprintf(tszSDBuffer, _X("257 \"%s\" is current directory.\r\n"), tszAliDir); + } + else + { + nSDLen = _xstprintf(tszSDBuffer, _X("257 \"/\" is current directory.\r\n")); + } + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,查询当前目录:%s 成功"), lpszClientAddr, tszRVBuffer); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,查询当前目录:%s,别名:%s 成功"), lpszClientAddr, tszFTPDir, tszAliDir); } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CWD))) { - nSDLen = _xstprintf(tszSDBuffer, _X("250 Directory changed to %s\r\n"), pSt_KeyValue->tszStrVlu); + XCHAR tszAliDir[XPATH_MAX] = {}; + Session_FTP_Get(lpszClientAddr, NULL, NULL, tszAliDir); + + if (_tcsxlen(tszAliDir) > 1) + { + _tcsxcat(tszAliDir, _X("/")); + _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); + } + else + { + _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); + } + Session_FTP_Set(lpszClientAddr, NULL, NULL, tszAliDir); + + nSDLen = _xstprintf(tszSDBuffer, _X("250 Directory changed to %s\r\n"), tszAliDir); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - Session_FTP_Set(lpszClientAddr, NULL, pSt_KeyValue->tszStrVlu); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, tszAliDir); } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CDUP, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CDUP))) { - nSDLen = _xstprintf(tszSDBuffer, _X("200 Directory changed to %s\r\n"), pSt_KeyValue->tszStrVlu); + XCHAR tszPDir[XPATH_MAX] = {}; + XCHAR tszAlisDir[XPATH_MAX] = {}; + Session_FTP_Get(lpszClientAddr, NULL, NULL, tszAlisDir); + //是否设置过目录 + if (_tcsxlen(tszAlisDir) <= 1) + { + //不允许切换更上级目录 + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_550, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + return false; + } + else + { + //如果路径是父目录 + BaseLib_String_GetFileAndPathA(tszAlisDir, tszPDir); + Session_FTP_Set(lpszClientAddr, NULL, NULL, tszPDir); + } + + nSDLen = _xstprintf(tszSDBuffer, _X("200 Directory changed to %s\r\n"), tszPDir); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - Session_FTP_Set(lpszClientAddr, NULL, pSt_KeyValue->tszStrVlu); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换上级目录:%s 成功"), lpszClientAddr, tszPDir); } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_LIST, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_LIST))) { //列举目录请求 XCHAR tszFilePath[XPATH_MAX] = {}; - if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath)) + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求列举目录:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); return false; } - if (_tcsxlen(tszFilePath) <= 0) + if (_tcsxlen(tszAlisPath) <= 0) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); @@ -285,7 +309,7 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - std::thread m_ThreadFTPFiles(XEngine_FTPFile_Thread, xstring(lpszClientAddr), xstring(tszFilePath)); + std::thread m_ThreadFTPFiles(XEngine_FTPFile_Thread, xstring(lpszClientAddr), xstring(tszFilePath), xstring(tszAlisPath)); m_ThreadFTPFiles.detach(); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,列举目录:%s 成功"), lpszClientAddr, tszFilePath); } diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp index 5ae32d29..570f8d10 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.cpp @@ -551,7 +551,7 @@ int main(int argc, char** argv) XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动WEBDAV任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nWebdavThread); } //FTP - if (st_ServiceCfg.nFTPCPort > 0) + if (st_ServiceCfg.nFTPPort > 0) { //控制端口启用 xhFTPContral = FTPProtocol_Parse_InitEx(lpszFTPCodes, st_ServiceCfg.st_XMax.nFTPThread); @@ -562,13 +562,13 @@ int main(int argc, char** argv) } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP服务成功,IO线程个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); - xhNetFTPContral = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPCPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); + xhNetFTPContral = NetCore_TCPXCore_StartEx(st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nMaxClient, st_ServiceCfg.st_XMax.nIOThread, false, st_ServiceCfg.bReuseraddr); if (NULL == xhNetFTPContral) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPCPort, NetCore_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动服务中,启动FTP网络服务失败,端口:%d,错误:%lX"), st_ServiceCfg.nFTPPort, NetCore_GetLastError()); goto XENGINE_EXITAPP; } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPCPort, st_ServiceCfg.st_XMax.nIOThread); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP网络服务成功,端口:%d,IO线程个数:%d"), st_ServiceCfg.nFTPPort, st_ServiceCfg.st_XMax.nIOThread); NetCore_TCPXCore_RegisterCallBackEx(xhNetFTPContral, XEngine_Callback_FTPContralLogin, XEngine_Callback_FTPContralRecv, XEngine_Callback_FTPContralLeave); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,注册FTP网络服务事件成功!")); @@ -589,8 +589,8 @@ int main(int argc, char** argv) } XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动FTP任务处理线程池成功,线程池个数:%d"), st_ServiceCfg.st_XMax.nFTPThread); - APIHelp_Port_Init(st_ServiceCfg.nFTPDStart, st_ServiceCfg.nFTPDEnd); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP数据端口成功,端口范围:%d-%d"), st_ServiceCfg.nFTPDStart, st_ServiceCfg.nFTPDEnd); + APIHelp_Port_Init(st_ServiceCfg.st_XFtp.nFTPStart, st_ServiceCfg.st_XFtp.nFTPEnd); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,初始化FTP数据端口成功,端口范围:%d-%d"), st_ServiceCfg.st_XFtp.nFTPStart, st_ServiceCfg.st_XFtp.nFTPEnd); } //只有使用了数据库,才启用P2P if (st_ServiceCfg.st_P2xp.bEnable) From a4b76a488a540f367498415b37e89aabdc6084fd Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sat, 12 Sep 2026 19:51:53 +0800 Subject: [PATCH 06/33] modify:upload and download support now for ftp --- .../XEngine_StorageApp/StorageApp_FTP.cpp | 174 ++++++++++++++++-- 1 file changed, 154 insertions(+), 20 deletions(-) diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 226ca3a6..9f042e30 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -31,21 +31,37 @@ XHTHREAD XCALLBACK XEngine_FTPContral_Thread(XPVOID lParam) } return 0; } -static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName) +static void XEngine_FTPDownload_Thread(xstring lpszClientAddr, xstring lpszFileName) { - XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr); + int nPort = 0; + XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); if (NULL == xhToken) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr, Session_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); + return; + } + int nMSGLen = 0; + XLONG dwEvent = XENGINE_NETCORE_TCP_SELECT_EVENT_LOGIN; + XCHAR tszClientAddr[XPATH_MIN] = {}; + XCHAR tszMSGBuffer[XPATH_8MAX] = {}; + if (!NetCore_TCPSelect_ReadIOEventEx(xhToken, tszClientAddr, tszMSGBuffer, &nMSGLen, &dwEvent)) + { + //关闭连接 + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); return; } - FILE* pSt_File = _xtfopen(lpszFileName, _X("rb")); + + FILE* pSt_File = _xtfopen(lpszFileName.c_str(), _X("rb")); if (NULL == pSt_File) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,打开文件:%s失败"), lpszClientAddr, lpszFileName); + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,打开文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); return; } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备数据文件:%s 完成,开始发送数据"), lpszClientAddr, lpszFileName); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备数据文件:%s 完成,开始发送数据"), lpszClientAddr.c_str(), lpszFileName.c_str()); while (true) { @@ -57,7 +73,7 @@ static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName } while (true) { - if (NetCore_TCPSelect_SendEx(xhToken, lpszClientAddr, tszMSGBuffer, nRet)) + if (NetCore_TCPSelect_SendEx(xhToken, tszClientAddr, tszMSGBuffer, nRet)) { break; } @@ -65,9 +81,13 @@ static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName { if (ERROR_XENGINE_BASELIB_BASELIB_IO_CLOSE == NetCore_GetLastError()) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,网络连接已断开"), lpszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,网络连接已断开"), lpszClientAddr.c_str()); break; } + else + { + fseek(pSt_File, -nRet, SEEK_CUR); + } } //等待10毫秒 std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -75,13 +95,68 @@ static void XEngine_FTPDatas_Thread(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName } fclose(pSt_File); //关闭连接 + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + //通知 + int nSDLen = 1024; + XCHAR tszSDBuffer[1024] = {}; + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s 文件发送数据完毕,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr); +} +static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileName) +{ + int nPort = 0; + XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); + if (NULL == xhToken) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); + return; + } + + FILE* pSt_File = _xtfopen(lpszFileName.c_str(), _X("wb")); + if (NULL == pSt_File) + { + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,创建文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); + return; + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,创建文件:%s 成功,开始接受数据"), lpszClientAddr.c_str(), lpszFileName.c_str()); + + while (true) + { + int nMSGLen = 0; + XLONG dwEvent = XENGINE_NETCORE_TCP_SELECT_EVENT_ALL; + XCHAR tszClientAddr[XPATH_MIN] = {}; + XCHAR tszMSGBuffer[XENGINE_SOCKET_SR_TCP_BUFFSIZE] = {}; + + if (!NetCore_TCPSelect_ReadIOEventEx(xhToken, tszClientAddr, tszMSGBuffer, &nMSGLen, &dwEvent)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + if (XENGINE_NETCORE_TCP_SELECT_EVENT_LEAVE == dwEvent) + { + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件,用户:%s 离开"), lpszClientAddr.c_str(), tszClientAddr); + break; + } + else if (XENGINE_NETCORE_TCP_SELECT_EVENT_RECV == dwEvent) + { + fwrite(tszMSGBuffer, 1, nMSGLen, pSt_File); + } + } + fclose(pSt_File); + //关闭连接 + APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); //通知 int nSDLen = 1024; XCHAR tszSDBuffer[1024] = {}; FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); - XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,文件发送数据完毕,网络连接已断开"), lpszClientAddr); + XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s 文件发送数据完毕,网络连接已断开"), lpszClientAddr.c_str()); } static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, xstring lpszAlisPath) { @@ -89,8 +164,6 @@ static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); if (NULL == xhToken) { - //关闭连接 - APIHelp_Port_Free(nPort); //还原端口 XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); return; } @@ -101,8 +174,8 @@ static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, if (!NetCore_TCPSelect_ReadIOEventEx(xhToken, tszClientAddr, tszMSGBuffer, &nMSGLen, &dwEvent)) { //关闭连接 + APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); - APIHelp_Port_Free(nPort); //还原端口 XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); return; } @@ -114,16 +187,19 @@ static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, _xstprintf(tszCMDBuffer, _X("dir %s"), tszDIRBuffer); SystemApi_Process_ReadCmdReturn(tszCMDBuffer, tszMSGBuffer, 0, 6, &nMSGLen, 3); + nMSGLen += 2; + _tcsxcat(tszMSGBuffer, _X("\r\n")); + if (!NetCore_TCPSelect_SendEx(xhToken, tszClientAddr, tszMSGBuffer, nMSGLen)) { //关闭连接 + APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); - APIHelp_Port_Free(nPort); //还原端口 XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,连接地址:%s,发送数据大小:%d 失败,错误码:%lX"), lpszClientAddr.c_str(), tszClientAddr, nMSGLen, NetCore_GetLastError()); } //关闭连接 + APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); - APIHelp_Port_Free(nPort); //还原端口 //通知 int nSDLen = 1024; XCHAR tszSDBuffer[1024] = {}; @@ -197,11 +273,31 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETR))) { - if (0 != _xtaccess(pSt_KeyValue->tszStrVlu, 0)) + //下载 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求下载文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + XCHAR tszFileName[XPATH_MAX] = {}; + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); + + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + + if (0 != _xtaccess(tszFileName, 0)) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_550, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求文件:%s 失败,文件不存在"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求文件:%s 失败,文件不存在"), lpszClientAddr, tszFileName); return false; } if (!Session_FTP_Set(lpszClientAddr, pSt_KeyValue->tszStrVlu)) @@ -211,8 +307,43 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求文件:%s 失败,没有设置服务器端口信息"), lpszClientAddr, pSt_KeyValue->tszStrVlu); return false; } + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + + std::thread m_ThreadFTPDatas(XEngine_FTPDownload_Thread, xstring(lpszClientAddr), xstring(tszFileName)); + m_ThreadFTPDatas.detach(); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求文件成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_STOR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_STOR))) + { + //上传 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + XCHAR tszFileName[XPATH_MAX] = {}; + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); - std::thread m_ThreadFTPDatas(XEngine_FTPDatas_Thread, lpszClientAddr, pSt_KeyValue->tszStrVlu); + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + + if (!Session_FTP_Set(lpszClientAddr, pSt_KeyValue->tszStrVlu)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,没有设置服务器端口信息"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + std::thread m_ThreadFTPDatas(XEngine_FTPUPload_Thread, xstring(lpszClientAddr), xstring(tszFileName)); m_ThreadFTPDatas.detach(); FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_150, tszSDBuffer, &nSDLen); @@ -250,8 +381,11 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in if (_tcsxlen(tszAliDir) > 1) { - _tcsxcat(tszAliDir, _X("/")); - _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); + if (0 != _tcsxncmp(pSt_KeyValue->tszStrVlu, tszAliDir, _tcsxlen(tszAliDir))) + { + _tcsxcat(tszAliDir, _X("/")); + _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); + } } else { From 00e0e105069cdbb3017e756cca9c1f40c201da9a Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sun, 13 Sep 2026 13:27:28 +0800 Subject: [PATCH 07/33] added:resume file download and upload support for ftp --- .../StorageModule_Session/Session_Define.h | 38 ++++++++++ .../StorageModule_Session.def | 2 + XEngine_Source/StorageModule_Session/pch.cpp | 8 ++ .../XEngine_StorageApp/StorageApp_FTP.cpp | 76 ++++++++++++++----- 4 files changed, 105 insertions(+), 19 deletions(-) diff --git a/XEngine_Source/StorageModule_Session/Session_Define.h b/XEngine_Source/StorageModule_Session/Session_Define.h index ac2f1261..4a88ca07 100644 --- a/XEngine_Source/StorageModule_Session/Session_Define.h +++ b/XEngine_Source/StorageModule_Session/Session_Define.h @@ -762,6 +762,44 @@ extern "C" bool Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE *********************************************************************/ extern "C" XHANDLE Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port = NULL); /******************************************************************** +函数名称:Session_FTP_SetRetr +函数功能:设置断点续传 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:nPos + In/Out:In + 类型:整数型 + 可空:N + 意思:输入断点续传位置 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_SetRetr(LPCXSTR lpszClientAddr, __int64u nPos); +/******************************************************************** +函数名称:Session_FTP_GetRetr +函数功能:获取断点续传 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:pInt_Pos + In/Out:Out + 类型:整数型指针 + 可空:Y + 意思:输出断点续传位置 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +extern "C" bool Session_FTP_GetRetr(LPCXSTR lpszClientAddr, __int64u* pInt_Pos); +/******************************************************************** 函数名称:Session_FTP_Delete 函数功能:删除一个会话 参数.一:lpszClientAddr diff --git a/XEngine_Source/StorageModule_Session/StorageModule_Session.def b/XEngine_Source/StorageModule_Session/StorageModule_Session.def index 73607b80..65fdf40c 100644 --- a/XEngine_Source/StorageModule_Session/StorageModule_Session.def +++ b/XEngine_Source/StorageModule_Session/StorageModule_Session.def @@ -43,4 +43,6 @@ EXPORTS Session_FTP_Get Session_FTP_SetSocket Session_FTP_GetSocket + Session_FTP_SetRetr + Session_FTP_GetRetr Session_FTP_Delete \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Session/pch.cpp b/XEngine_Source/StorageModule_Session/pch.cpp index a25dd6b8..0f6e3e37 100644 --- a/XEngine_Source/StorageModule_Session/pch.cpp +++ b/XEngine_Source/StorageModule_Session/pch.cpp @@ -190,6 +190,14 @@ extern "C" XHANDLE Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port) { return m_FTPContral.Session_FTP_GetSocket(lpszClientAddr, pInt_Port); } +extern "C" bool Session_FTP_SetRetr(LPCXSTR lpszClientAddr, __int64u nPos) +{ + return m_FTPContral.Session_FTP_SetRetr(lpszClientAddr, nPos); +} +extern "C" bool Session_FTP_GetRetr(LPCXSTR lpszClientAddr, __int64u* pInt_Pos) +{ + return m_FTPContral.Session_FTP_GetRetr(lpszClientAddr, pInt_Pos); +} extern "C" bool Session_FTP_Delete(LPCXSTR lpszClientAddr) { return m_FTPContral.Session_FTP_Delete(lpszClientAddr); diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 9f042e30..ef118165 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -37,7 +37,7 @@ static void XEngine_FTPDownload_Thread(xstring lpszClientAddr, xstring lpszFileN XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); if (NULL == xhToken) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求下载文件失败,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); return; } int nMSGLen = 0; @@ -49,19 +49,26 @@ static void XEngine_FTPDownload_Thread(xstring lpszClientAddr, xstring lpszFileN //关闭连接 APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求下载文件失败,获取网络事件失败,错误码:%lX"), lpszClientAddr.c_str(), NetCore_GetLastError()); return; } - FILE* pSt_File = _xtfopen(lpszFileName.c_str(), _X("rb")); if (NULL == pSt_File) { APIHelp_Port_Free(nPort); NetCore_TCPSelect_StopEx(xhToken); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,打开文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求下载文件失败,打开文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); return; } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备数据文件:%s 完成,开始发送数据"), lpszClientAddr.c_str(), lpszFileName.c_str()); + //设置断点续传 + __int64u nPos = 0; + Session_FTP_GetRetr(lpszClientAddr.c_str(), &nPos); + if (nPos > 0) + { + fseek(pSt_File, (long)nPos, SEEK_SET); + } + + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备数据文件:%s 完成,开始下载文件数据,下载位置:%llu"), lpszClientAddr.c_str(), lpszFileName.c_str(), nPos); while (true) { @@ -81,7 +88,7 @@ static void XEngine_FTPDownload_Thread(xstring lpszClientAddr, xstring lpszFileN { if (ERROR_XENGINE_BASELIB_BASELIB_IO_CLOSE == NetCore_GetLastError()) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,网络连接已断开"), lpszClientAddr.c_str()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求下载文件失败,网络连接已断开"), lpszClientAddr.c_str()); break; } else @@ -102,7 +109,7 @@ static void XEngine_FTPDownload_Thread(xstring lpszClientAddr, xstring lpszFileN XCHAR tszSDBuffer[1024] = {}; FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s 文件发送数据完毕,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,连接地址:%s 请求下载文件成功,网络连接已断开"), lpszClientAddr.c_str(), tszClientAddr); } static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileName) { @@ -110,20 +117,38 @@ static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileNam XHANDLE xhToken = Session_FTP_GetSocket(lpszClientAddr.c_str(), &nPort); if (NULL == xhToken) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件失败,获取网络连接失败,错误码:%lX"), lpszClientAddr.c_str(), Session_GetLastError()); return; } - - FILE* pSt_File = _xtfopen(lpszFileName.c_str(), _X("wb")); - if (NULL == pSt_File) + FILE* pSt_File = NULL; + //设置断点续传 + __int64u nPos = 0; + Session_FTP_GetRetr(lpszClientAddr.c_str(), &nPos); + if (nPos > 0) { - APIHelp_Port_Free(nPort); - NetCore_TCPSelect_StopEx(xhToken); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求被动模式失败,创建文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); - return; + pSt_File = _xtfopen(lpszFileName.c_str(), _X("rb+")); + if (NULL == pSt_File) + { + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件失败,续传文件不存在:%s"), lpszClientAddr.c_str(), lpszFileName.c_str()); + return; + } + fseek(pSt_File, (long)nPos, SEEK_SET); } - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,创建文件:%s 成功,开始接受数据"), lpszClientAddr.c_str(), lpszFileName.c_str()); - + else + { + pSt_File = _xtfopen(lpszFileName.c_str(), _X("wb")); + if (NULL == pSt_File) + { + APIHelp_Port_Free(nPort); + NetCore_TCPSelect_StopEx(xhToken); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件失败,创建文件:%s失败"), lpszClientAddr.c_str(), lpszFileName.c_str()); + return; + } + } + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,创建文件:%s 成功,开始接受数据,位置:%llu"), lpszClientAddr.c_str(), lpszFileName.c_str(), nPos); + while (true) { int nMSGLen = 0; @@ -139,7 +164,7 @@ static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileNam if (XENGINE_NETCORE_TCP_SELECT_EVENT_LEAVE == dwEvent) { - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,获取网络事件,用户:%s 离开"), lpszClientAddr.c_str(), tszClientAddr); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件失败,获取网络事件,用户:%s 离开"), lpszClientAddr.c_str(), tszClientAddr); break; } else if (XENGINE_NETCORE_TCP_SELECT_EVENT_RECV == dwEvent) @@ -156,7 +181,7 @@ static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileNam XCHAR tszSDBuffer[1024] = {}; FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_226, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr.c_str(), tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s 文件发送数据完毕,网络连接已断开"), lpszClientAddr.c_str()); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s 请求上传文件完毕,网络连接已断开"), lpszClientAddr.c_str()); } static void XEngine_FTPFile_Thread(xstring lpszClientAddr, xstring lpszFilePath, xstring lpszAlisPath) { @@ -350,6 +375,19 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求文件成功,请求信息:%s"), lpszClientAddr, pSt_KeyValue->tszStrVlu); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETR))) + { + if (!Session_FTP_SetRetr(lpszClientAddr, _ttxoll(pSt_KeyValue->tszStrVlu))) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + nSDLen = _xstprintf(tszSDBuffer, _X("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer.\r\n"), pSt_KeyValue->tszStrVlu); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求断点续传位置:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); From e99e230b1f68dd00163daf6a7551707542d0246a Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Mon, 14 Sep 2026 17:00:35 +0800 Subject: [PATCH 08/33] added:delete method support for ftp --- README.en.md | 1 + README.md | 1 + .../XEngine_StorageApp/StorageApp_FTP.cpp | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/README.en.md b/README.en.md index ccf3031b..9cef8d92 100644 --- a/README.en.md +++ b/README.en.md @@ -35,6 +35,7 @@ feature list: 18. support convert record(service upload and download) 19. webdav protocol download and upload file support 20. preview file create +21. FTP upload and download ## install diff --git a/README.md b/README.md index 609a7d8c..e67571be 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ c c++ file storage service 18. 支持转录(服务器上传下载) 19. 支持WEBDAV协议上传下载文件 20. 预创建文件 +21. FTP上传下载支持 ## 安装教程 diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index ef118165..f4b35399 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -377,6 +377,7 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETR))) { + //断点续传 if (!Session_FTP_SetRetr(lpszClientAddr, _ttxoll(pSt_KeyValue->tszStrVlu))) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); @@ -388,6 +389,33 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,请求断点续传位置:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_DELE, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_DELE))) + { + //删除文件 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + XCHAR tszFileName[XPATH_MAX] = {}; + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); + + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + _xtremove(tszFileName); + + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_250, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,删除文件成功:%s"), lpszClientAddr, tszFileName); + } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); From 9f273b7e54f2f88eba2f4aad2d3e0ed25984aabc Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Mon, 14 Sep 2026 20:09:50 +0800 Subject: [PATCH 09/33] added:mkdir and del dir method support for ftp --- .../XEngine_StorageApp/StorageApp_FTP.cpp | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index f4b35399..2bb8fb5d 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -424,6 +424,7 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PWD))) { + //列举目录 XCHAR tszFTPDir[XPATH_MAX] = {}; XCHAR tszAliDir[XPATH_MAX] = {}; Session_FTP_Get(lpszClientAddr, NULL, tszFTPDir, tszAliDir); @@ -442,12 +443,18 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CWD))) { + //切换目录 XCHAR tszAliDir[XPATH_MAX] = {}; Session_FTP_Get(lpszClientAddr, NULL, NULL, tszAliDir); - if (_tcsxlen(tszAliDir) > 1) + if (_tcsxlen(tszAliDir) > 0) { - if (0 != _tcsxncmp(pSt_KeyValue->tszStrVlu, tszAliDir, _tcsxlen(tszAliDir))) + //如果是根目录,//并且切换目标不是根目录 + if (1 == _tcsxlen(tszAliDir) && 1 != _tcsxlen(pSt_KeyValue->tszStrVlu)) + { + _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); + } + else if (0 != _tcsxncmp(pSt_KeyValue->tszStrVlu, tszAliDir, _tcsxlen(tszAliDir))) { _tcsxcat(tszAliDir, _X("/")); _tcsxcat(tszAliDir, pSt_KeyValue->tszStrVlu); @@ -463,6 +470,58 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,切换目录:%s 成功"), lpszClientAddr, tszAliDir); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_MKD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_MKD))) + { + //创建目录 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + XCHAR tszFileName[XPATH_MAX] = {}; + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + SystemApi_File_CreateMutilFolderA(tszFileName); + + nSDLen = _xstprintf(tszSDBuffer, _X("257 \"%s\" directory created\r\n"), pSt_KeyValue->tszStrVlu); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,创建目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RMD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RMD))) + { + //删除目录 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求上传文件:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + XCHAR tszFileName[XPATH_MAX] = {}; + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + SystemApi_File_DeleteMutilFolderA(tszFileName); + + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_250, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,删除目录:%s 成功"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_CDUP, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_CDUP))) { XCHAR tszPDir[XPATH_MAX] = {}; From fd7a220c074b2593d9cba8348a56440a04f9f30a Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 12:51:14 +0800 Subject: [PATCH 10/33] update:configure file --- XEngine_Release/XEngine_Config/FTPCode.types | 31 +++++++++++++++++++ .../XEngine_Config/XEngine_Config.json | 14 +++++++-- .../XEngine_Config/XEngine_VersionConfig.json | 1 - 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 XEngine_Release/XEngine_Config/FTPCode.types diff --git a/XEngine_Release/XEngine_Config/FTPCode.types b/XEngine_Release/XEngine_Config/FTPCode.types new file mode 100644 index 00000000..dc7e5042 --- /dev/null +++ b/XEngine_Release/XEngine_Config/FTPCode.types @@ -0,0 +1,31 @@ +//Response 1xx (Preliminary) +125:Data connection already open; transfer starting +150:File status okay; about to open data connection +//Response 2xx (Completion) +200:Command okay +202:Command not implemented, superflous at this site +211:System status, or system help reply +220:Service ready for new user +221:Service closing control connection +226:Closing data connection +227:Entering Passive Mode +230:User logged in, proceed +250:Requested file action okay, completed +257:PATHNAME created +//Response 3xx (Intermediate) +331:User name okay, need password +332:Need account for login +350:Requested file action pending further information +//Response 4xx (Transient Negative) +421:Service not available, closing control connection +425:Can't open data connection +426:Connection closed; transfer aborted +450:Requested file action not taken +451:Requested action aborted: local error in processing +//Response 5xx (Permanent Negative) +500:Syntax error, command unrecognized +501:Syntax error in parameters or arguments +502:Command not implemented +503:Bad sequence of commands +530:Not logged in +550:Requested action not taken \ No newline at end of file diff --git a/XEngine_Release/XEngine_Config/XEngine_Config.json b/XEngine_Release/XEngine_Config/XEngine_Config.json index e889a4e6..f80b7483 100644 --- a/XEngine_Release/XEngine_Config/XEngine_Config.json +++ b/XEngine_Release/XEngine_Config/XEngine_Config.json @@ -1,10 +1,11 @@ { - "tszIPAddr": "10.0.1.88", + "tszIPAddr": "127.0.0.1", "bDeamon": 0, "nCenterPort": 5100, "nStorageDLPort": 5101, "nStorageUPPort": 5102, "nWebdavPort": 5103, + "nFTPPort": 5104, "XMax": { "MaxClient": 10000, "MaxQueue": 10000, @@ -12,7 +13,8 @@ "CenterThread": 2, "nStorageUPThread": 2, "nStorageDLThread": 2, - "nWebdavThread": 2 + "nWebdavThread": 2, + "nFTPThread": 2 }, "XTime": { "bHBTime": 0, @@ -20,7 +22,8 @@ "nTimeCheck": 3, "nCenterTimeOut": 5, "nStorageTimeOut": 5, - "nWebdavTimeOut": 5 + "nWebdavTimeOut": 5, + "nFTPTimeOut": 5 }, "XLog": { "LogFile": "./XEngine_XLog/XEngine_StorageApp.log", @@ -84,5 +87,10 @@ "bEnable": true, "nSDMax": 0, "nRVMax": 0 + }, + "XFtp": { + "nFTPStart": 50051, + "nFTPEnd": 51051, + "tszFTPDir": "D:\\XEngine_Storage\\XEngine_Source\\Debug" } } \ No newline at end of file diff --git a/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json b/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json index fa912628..85c43b21 100644 --- a/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json +++ b/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json @@ -1,6 +1,5 @@ { "XVer":[ - "3.23.0.1001 Build20260807", "3.22.0.1001 Build20260427", "3.21.0.1001 Build20250923", "3.20.0.1001 Build20250620", From 180c2fd8cfcfa75306f271fbbc87f528faa1152d Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:01:26 +0800 Subject: [PATCH 11/33] added:session for ftp --- .../Session_FTPContral/Session_FTPContral.cpp | 335 ++++++++++++++++++ .../Session_FTPContral/Session_FTPContral.h | 43 +++ 2 files changed, 378 insertions(+) create mode 100644 XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.cpp create mode 100644 XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.h diff --git a/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.cpp b/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.cpp new file mode 100644 index 00000000..ea6606ce --- /dev/null +++ b/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.cpp @@ -0,0 +1,335 @@ +#include "pch.h" +#include "Session_FTPContral.h" +/******************************************************************** +// Created: 2024/08/05 16:14:56 +// File Name: D:\XEngine_Storage\XEngine_Source\StorageModule_Session\Session_FTPContral\Session_FTPContral.cpp +// File Path: D:\XEngine_Storage\XEngine_Source\StorageModule_Session\Session_FTPContral +// File Base: Session_FTPContral +// File Ext: cpp +// Project: XEngine +// Author: qyt +// Purpose: FTP会话 +// History: +*********************************************************************/ +CSession_FTPContral::CSession_FTPContral() +{ +} +CSession_FTPContral::~CSession_FTPContral() +{ +} +////////////////////////////////////////////////////////////////////////// +// 公有函数 +////////////////////////////////////////////////////////////////////////// +/******************************************************************** +函数名称:Session_FTP_Insert +函数功能:插入一个会话 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入客户端地址 + 参数.二:bUPLoader + In/Out:In + 类型:逻辑型 + 可空:N + 意思:输入是否为上传会话 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader) +{ + Session_IsErrorOccur = false; + + SESSION_FTPCONTRAL st_FTPSession = {}; + + st_FTPSession.nTimeStart = time(NULL); + st_FTPSession.bUPLoader = bUPLoader; + + st_Locker.lock(); + stl_MapAction.insert(make_pair(lpszClientAddr, st_FTPSession)); + st_Locker.unlock(); + return true; +} +/******************************************************************** +函数名称:Session_FTP_Set +函数功能:设置 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入客户端地址 + 参数.二:lpszFileName + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入要操作的文件 + 参数.三:lpszFilePath + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入要操作的文件路径 + 参数.四:lpszAlisName + In/Out:In + 类型:常量字符指针 + 可空:Y + 意思:输入路径别名 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName, LPCXSTR lpszFilePath, LPCXSTR lpszAlisName) +{ + Session_IsErrorOccur = false; + + std::shared_lock st_AutoLock(st_Locker); + + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return NULL; + } + + if (NULL != lpszFileName) + { + memset(stl_MapIterator->second.tszFileName, '\0', sizeof(stl_MapIterator->second.tszFileName)); + _tcsxcpy(stl_MapIterator->second.tszFileName, lpszFileName); + } + if (NULL != lpszFilePath) + { + memset(stl_MapIterator->second.tszFilePath, '\0', sizeof(stl_MapIterator->second.tszFilePath)); + _tcsxcpy(stl_MapIterator->second.tszFilePath, lpszFilePath); + } + if (NULL != lpszAlisName) + { + memset(stl_MapIterator->second.tszAlisPath, '\0', sizeof(stl_MapIterator->second.tszAlisPath)); + _tcsxcpy(stl_MapIterator->second.tszAlisPath, lpszAlisName); + } + return true; +} +/******************************************************************** +函数名称:Session_FTP_Get +函数功能:获取信息 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:ptszFileName + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的文件 + 参数.三:ptszFilePath + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的文件路径 + 参数.四:ptszAlisPath + In/Out:Out + 类型:字符指针 + 可空:Y + 意思:输出操作的路径别名 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName, XCHAR* ptszFilePath, XCHAR* ptszAlisPath) +{ + Session_IsErrorOccur = false; + + std::shared_lock st_AutoLock(st_Locker); + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return NULL; + } + + if (NULL != ptszFilePath) + { + _tcsxcpy(ptszFilePath, stl_MapIterator->second.tszFilePath); + } + if (NULL != ptszFileName) + { + _tcsxcpy(ptszFileName, stl_MapIterator->second.tszFileName); + } + if (NULL != ptszAlisPath) + { + _tcsxcpy(ptszAlisPath, stl_MapIterator->second.tszAlisPath); + } + return true; +} +/******************************************************************** +函数名称:Session_FTP_SetSocket +函数功能:设置网络句柄 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:nPort + In/Out:In + 类型:整数型 + 可空:N + 意思:输入操作的端口 + 参数.二:xhToken + In/Out:In + 类型:句柄 + 可空:N + 意思:输入操作的套接字句柄 +返回值 + 类型:逻辑型 + 意思:是否设置成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE xhToken) +{ + Session_IsErrorOccur = false; + std::shared_lock st_AutoLock(st_Locker); + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return NULL; + } + stl_MapIterator->second.nPort = nPort; + stl_MapIterator->second.xhToken = xhToken; + return true; +} +/******************************************************************** +函数名称:Session_FTP_GetSocket +函数功能:获取网络句柄 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:pInt_Port + In/Out:Out + 类型:整数型指针 + 可空:Y + 意思:输出操作的端口 +返回值 + 类型:句柄 + 意思:返回获取到的网络句柄 +备注: +*********************************************************************/ +XHANDLE CSession_FTPContral::Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port) +{ + Session_IsErrorOccur = false; + + std::shared_lock st_AutoLock(st_Locker); + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return NULL; + } + + if (NULL != pInt_Port) + { + *pInt_Port = stl_MapIterator->second.nPort; + } + return stl_MapIterator->second.xhToken; +} +/******************************************************************** +函数名称:Session_FTP_SetRetr +函数功能:设置断点续传 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:nPos + In/Out:In + 类型:整数型 + 可空:N + 意思:输入断点续传位置 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_SetRetr(LPCXSTR lpszClientAddr, __int64u nPos) +{ + Session_IsErrorOccur = false; + + std::shared_lock st_AutoLock(st_Locker); + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return false; + } + stl_MapIterator->second.nPos = nPos; + return true; +} +/******************************************************************** +函数名称:Session_FTP_GetRetr +函数功能:获取断点续传 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 + 参数.二:pInt_Pos + In/Out:Out + 类型:整数型指针 + 可空:Y + 意思:输出断点续传位置 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_GetRetr(LPCXSTR lpszClientAddr, __int64u* pInt_Pos) +{ + Session_IsErrorOccur = false; + + std::shared_lock st_AutoLock(st_Locker); + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator == stl_MapAction.end()) + { + Session_IsErrorOccur = true; + Session_dwErrorCode = ERROR_STORAGE_MODULE_SESSION_NOTFOUND; + return false; + } + *pInt_Pos = stl_MapIterator->second.nPort; + return true; +} +/******************************************************************** +函数名称:Session_FTP_Delete +函数功能:删除一个会话 + 参数.一:lpszClientAddr + In/Out:In + 类型:常量字符指针 + 可空:N + 意思:输入要操作的客户端地址 +返回值 + 类型:逻辑型 + 意思:是否成功 +备注: +*********************************************************************/ +bool CSession_FTPContral::Session_FTP_Delete(LPCXSTR lpszClientAddr) +{ + Session_IsErrorOccur = false; + + std::unique_lock st_AutoLock(st_Locker); + + unordered_map::iterator stl_MapIterator = stl_MapAction.find(lpszClientAddr); + if (stl_MapIterator != stl_MapAction.end()) + { + stl_MapAction.erase(stl_MapIterator); + } + return true; +} \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.h b/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.h new file mode 100644 index 00000000..f99c69b9 --- /dev/null +++ b/XEngine_Source/StorageModule_Session/Session_FTPContral/Session_FTPContral.h @@ -0,0 +1,43 @@ +#pragma once +/******************************************************************** +// Created: 2024/08/05 16:14:56 +// File Name: D:\XEngine_Storage\XEngine_Source\StorageModule_Session\Session_FTPContral\Session_FTPContral.h +// File Path: D:\XEngine_Storage\XEngine_Source\StorageModule_Session\Session_FTPContral +// File Base: Session_FTPContral +// File Ext: h +// Project: XEngine +// Author: qyt +// Purpose: FTP会话 +// History: +*********************************************************************/ +typedef struct +{ + XCHAR tszFileName[XPATH_MAX]; + XCHAR tszFilePath[XPATH_MAX]; + XCHAR tszAlisPath[XPATH_MAX]; + __int64u nPos; + time_t nTimeStart; + int nPort; + bool bUPLoader; + XHANDLE xhToken; +}SESSION_FTPCONTRAL; + +class CSession_FTPContral +{ +public: + CSession_FTPContral(); + ~CSession_FTPContral(); +public: + bool Session_FTP_Insert(LPCXSTR lpszClientAddr, bool bUPLoader); + bool Session_FTP_Set(LPCXSTR lpszClientAddr, LPCXSTR lpszFileName = NULL, LPCXSTR lpszFilePath = NULL, LPCXSTR lpszAlisName = NULL); + bool Session_FTP_Get(LPCXSTR lpszClientAddr, XCHAR* ptszFileName = NULL, XCHAR* ptszFilePath = NULL, XCHAR* ptszAlisPath = NULL); + bool Session_FTP_SetSocket(LPCXSTR lpszClientAddr, int nPort, XHANDLE xhToken); + XHANDLE Session_FTP_GetSocket(LPCXSTR lpszClientAddr, int* pInt_Port = NULL); + bool Session_FTP_SetRetr(LPCXSTR lpszClientAddr, __int64u nPos); + bool Session_FTP_GetRetr(LPCXSTR lpszClientAddr, __int64u* pInt_Pos); + bool Session_FTP_Delete(LPCXSTR lpszClientAddr); +private: + shared_mutex st_Locker; +private: + unordered_map stl_MapAction; +}; \ No newline at end of file From a0a0fe60b4ef1e3c00b1b21d8e1832a650aec019 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:06:38 +0800 Subject: [PATCH 12/33] fixed:build error --- XEngine_Source/XEngine_StorageApp/Makefile | 6 +++--- XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/XEngine_Source/XEngine_StorageApp/Makefile b/XEngine_Source/XEngine_StorageApp/Makefile index bdeb0de1..0accd79d 100644 --- a/XEngine_Source/XEngine_StorageApp/Makefile +++ b/XEngine_Source/XEngine_StorageApp/Makefile @@ -8,7 +8,7 @@ LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -ljsoncpp -ltinyxml2 -lXEngine_InfoReport -lXEngine_Verification LIBEX = OBJECTS = Storage_TaskPass.o Storage_TaskManage.o Storage_TaskP2p.o Storage_TaskAction.o \ - StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o XStorageApp_FTP.o Engine_StorageApp.o + StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o StorageApp_FTP.o Engine_StorageApp.o ifeq ($(RELEASE),1) FLAGS = -c -D _RELEASE @@ -61,8 +61,8 @@ StorageApp_UPLoader.o:./StorageApp_UPLoader.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./StorageApp_UPLoader.cpp StorageApp_Webdav.o:./StorageApp_Webdav.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./StorageApp_Webdav.cpp -XStorageApp_FTP.o:./XStorageApp_FTP.cpp - $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./XStorageApp_FTP.cpp +StorageApp_FTP.o:./StorageApp_FTP.cpp + $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./StorageApp_FTP.cpp Storage_TaskPass.o:./Storage_APPTask/Storage_TaskPass.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./Storage_APPTask/Storage_TaskPass.cpp diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp index ef6c5a8f..baa561a4 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp @@ -172,7 +172,6 @@ bool XCALLBACK XEngine_Callback_FTPContralLogin(LPCXSTR lpszClientAddr, XSOCKET int nSDLen = 0; XCHAR tszSDBuffer[XPATH_MIN] = {}; - XENGINE_KEYVALUE st_KeyValue = {}; FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_220, tszSDBuffer, &nSDLen); XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, STORAGE_NETTYPE_FTPCONTRAL); From ba3c8f4f75430d6d3fa42a522647e60f44306c22 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:12:41 +0800 Subject: [PATCH 13/33] fixed:make file build error --- XEngine_Source/XEngine_StorageApp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XEngine_Source/XEngine_StorageApp/Makefile b/XEngine_Source/XEngine_StorageApp/Makefile index 0accd79d..a3a3291e 100644 --- a/XEngine_Source/XEngine_StorageApp/Makefile +++ b/XEngine_Source/XEngine_StorageApp/Makefile @@ -8,7 +8,7 @@ LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -ljsoncpp -ltinyxml2 -lXEngine_InfoReport -lXEngine_Verification LIBEX = OBJECTS = Storage_TaskPass.o Storage_TaskManage.o Storage_TaskP2p.o Storage_TaskAction.o \ - StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o StorageApp_FTP.o Engine_StorageApp.o + StorageApp_Config.o StorageApp_HTTPHelp.o StorageApp_Download.o StorageApp_Network.o StorageApp_Center.o StorageApp_UPLoader.o StorageApp_Webdav.o StorageApp_FTP.o XEngine_StorageApp.o ifeq ($(RELEASE),1) FLAGS = -c -D _RELEASE From 467edafc01ea74e090922a38f5b91929e02114c0 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:36:20 +0800 Subject: [PATCH 14/33] fixed:link error --- XEngine_Source/XEngine_StorageApp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XEngine_Source/XEngine_StorageApp/Makefile b/XEngine_Source/XEngine_StorageApp/Makefile index a3a3291e..da13f047 100644 --- a/XEngine_Source/XEngine_StorageApp/Makefile +++ b/XEngine_Source/XEngine_StorageApp/Makefile @@ -3,7 +3,7 @@ PLATVER = LOADHDR = -I ./ LOADSO = -L ../XEngine_DependLibrary/XEngine_Module/jsoncpp -L ../XEngine_DependLibrary/XEngine_Module/tinyxml2 -L ../XEngine_DependLibrary/XEngine_Module/XEngine_InfoReport -L ../XEngine_DependLibrary/XEngine_Module/XEngine_Verification \ -L ../StorageModule_Database -L ../StorageModule_Config -L ../StorageModule_Session -L ../StorageModule_APIHelp -L ../StorageModule_Protocol -L ../StorageModule_BTorrent -LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -lXEngine_Cryption -lXClient_APIHelp -lXEngine_SystemApi -lHelpComponents_XLog -lHelpComponents_Packets -lRfcComponents_HttpProtocol \ +LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -lXEngine_Cryption -lXClient_APIHelp -lNetHelp_APIAddr -lXEngine_SystemApi -lHelpComponents_XLog -lHelpComponents_Packets -lRfcComponents_HttpProtocol -lRfcComponents_FTPProtocol \ -lStorageModule_Database -lStorageModule_Config -lStorageModule_Session -lStorageModule_APIHelp -lStorageModule_Protocol -lStorageModule_BTorrent \ -ljsoncpp -ltinyxml2 -lXEngine_InfoReport -lXEngine_Verification LIBEX = From b67520223385653e6ed860db55310e02ca73f2f5 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:43:33 +0800 Subject: [PATCH 15/33] fixed:build --- XEngine_Source/StorageModule_Session/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/XEngine_Source/StorageModule_Session/Makefile b/XEngine_Source/StorageModule_Session/Makefile index 1cb6ef75..95389461 100644 --- a/XEngine_Source/StorageModule_Session/Makefile +++ b/XEngine_Source/StorageModule_Session/Makefile @@ -6,7 +6,7 @@ LOADHDR = -I ./ LOADSO = LIB = -lXEngine_BaseLib -lNetHelp_APIAddr LIBEX = -OBJECTS = Session_DLStroage.o Session_UPStroage.o Session_Action.o Session_Webdav.o pch.o +OBJECTS = Session_DLStroage.o Session_UPStroage.o Session_Action.o Session_Webdav.o Session_FTPContral.o pch.o ifeq ($(RELEASE),1) FLAGS = -c -D _RELEASE @@ -58,6 +58,8 @@ Session_Action.o:./Session_Action/Session_Action.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./Session_Action/Session_Action.cpp Session_Webdav.o:./Session_Webdav/Session_Webdav.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./Session_Webdav/Session_Webdav.cpp +Session_FTPContral.o:./Session_FTPContral/Session_FTPContral.cpp + $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./Session_FTPContral/Session_FTPContral.cpp pch.o:./pch.cpp $(CC) $(DEBUG) $(FLAGS) $(UNICODE) $(PLATVER) $(LOADHDR) ./pch.cpp From b8b95e2cb55cad2d9362161c8c0982dbfc01ace6 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 13:50:12 +0800 Subject: [PATCH 16/33] fixed:build --- XEngine_Source/StorageModule_Session/pch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/XEngine_Source/StorageModule_Session/pch.h b/XEngine_Source/StorageModule_Session/pch.h index 799b6f9c..5ac8b7c7 100644 --- a/XEngine_Source/StorageModule_Session/pch.h +++ b/XEngine_Source/StorageModule_Session/pch.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include using namespace std; From def62d57e65618dc4de0469d3331b89de4afe444 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Tue, 15 Sep 2026 14:14:36 +0800 Subject: [PATCH 17/33] ci:update mac build --- .github/workflows/macbuild.yml | 13 ++++--------- XEngine_Source/VSCopy_Arm64.bat | 3 ++- XEngine_Source/VSCopy_x64.bat | 3 ++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/macbuild.yml b/.github/workflows/macbuild.yml index d44eba69..873a9ea4 100644 --- a/.github/workflows/macbuild.yml +++ b/.github/workflows/macbuild.yml @@ -17,8 +17,6 @@ jobs: strategy: matrix: include: - - os: macos-15 - - os: macos-15-intel - os: macos-26 - os: macos-26-intel runs-on: ${{ matrix.os }} @@ -41,20 +39,17 @@ jobs: git submodule init git submodule update - - name: brew install - run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - - name: Set TERM variable run: echo "TERM=xterm" >> $GITHUB_ENV - name: Set up Dependency x86_64 Environment - if: matrix.os == 'macos-15-intel' || matrix.os == 'macos-26-intel' + if: matrix.os == 'macos-26-intel' run: | cd libxengine chmod 777 * ./XEngine_LINEnv.sh -i 3 - name: Set up Dependency Arm64 Environment - if: matrix.os == 'macos-15' || matrix.os == 'macos-26' + if: matrix.os == 'macos-26' run: | latest_tag=$(git ls-remote --tags --sort="v:refname" https://github.com/libxengine/libxengine.git | awk '{print $2}' | sed 's/refs\/tags\///' | tail -n 1) wget https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_Mac_Arm64.zip @@ -82,14 +77,14 @@ jobs: ./XEngine_XStorageApp -t - name: Upload folder as artifact with mac x64 - if: matrix.os == 'macos-15-intel' + if: matrix.os == 'macos-26-intel' uses: actions/upload-artifact@v7 with: name: XEngine_XStorageApp-Mac_x86_64 path: XEngine_Release/ retention-days: 1 - name: Upload folder as artifact with mac arm - if: matrix.os == 'macos-15' + if: matrix.os == 'macos-26' uses: actions/upload-artifact@v7 with: name: XEngine_XStorageApp-Mac_Arm64 diff --git a/XEngine_Source/VSCopy_Arm64.bat b/XEngine_Source/VSCopy_Arm64.bat index 450a7fb7..eec2b63d 100644 --- a/XEngine_Source/VSCopy_Arm64.bat +++ b/XEngine_Source/VSCopy_Arm64.bat @@ -16,4 +16,5 @@ copy /y "%XEngine_LibArm64%\XEngine_NetHelp\NetHelp_XSocket.dll" "./" copy /y "%XEngine_LibArm64%\XEngine_HelpComponents\HelpComponents_DataBase.dll" "./" copy /y "%XEngine_LibArm64%\XEngine_HelpComponents\HelpComponents_XLog.dll" "./" -copy /y "%XEngine_LibArm64%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" \ No newline at end of file +copy /y "%XEngine_LibArm64%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" +copy /y "%XEngine_LibArm64%\XEngine_RfcComponents\RfcComponents_FTPProtocol.dll" "./" \ No newline at end of file diff --git a/XEngine_Source/VSCopy_x64.bat b/XEngine_Source/VSCopy_x64.bat index 008422e6..efaa2553 100644 --- a/XEngine_Source/VSCopy_x64.bat +++ b/XEngine_Source/VSCopy_x64.bat @@ -16,4 +16,5 @@ copy /y "%XEngine_Lib64%\XEngine_NetHelp\NetHelp_XSocket.dll" "./" copy /y "%XEngine_Lib64%\XEngine_HelpComponents\HelpComponents_DataBase.dll" "./" copy /y "%XEngine_Lib64%\XEngine_HelpComponents\HelpComponents_XLog.dll" "./" -copy /y "%XEngine_Lib64%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" \ No newline at end of file +copy /y "%XEngine_Lib64%\XEngine_RfcComponents\RfcComponents_HttpProtocol.dll" "./" +copy /y "%XEngine_Lib64%\XEngine_RfcComponents\RfcComponents_FTPProtocol.dll" "./" \ No newline at end of file From aa6e226cc677cd8a9340b58cbd1fe55c8f3203fd Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 09:15:12 +0800 Subject: [PATCH 18/33] update:depend library --- XEngine_Source/XEngine_DependLibrary | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XEngine_Source/XEngine_DependLibrary b/XEngine_Source/XEngine_DependLibrary index dc73149a..b0cba1b2 160000 --- a/XEngine_Source/XEngine_DependLibrary +++ b/XEngine_Source/XEngine_DependLibrary @@ -1 +1 @@ -Subproject commit dc73149af8aef839085e1f7ad254aea41f0a2f86 +Subproject commit b0cba1b26f00b3c71c42f7f6fdc201d164590cd7 From 5e5dd24588567fc0242d27e59fc57baa269e9f48 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 09:17:36 +0800 Subject: [PATCH 19/33] ci:add open suse support --- .github/workflows/suse_build.yml | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/suse_build.yml diff --git a/.github/workflows/suse_build.yml b/.github/workflows/suse_build.yml new file mode 100644 index 00000000..1ec4129a --- /dev/null +++ b/.github/workflows/suse_build.yml @@ -0,0 +1,91 @@ +name: opensuse build workflows + +on: + push: + branches: + - 'develop' + paths: + - 'XEngine_Source/**' + - 'XEngine_Release/**' + - '.github/**' + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.runner }} + container: + image: opensuse/leap:${{ matrix.version }} + options: --platform=${{ matrix.platform }} + strategy: + matrix: + include: + - arch: amd64 + runner: ubuntu-24.04 + platform: linux/amd64 + version: 16.0 + artifact: x86-64 + - arch: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64 + version: 16.0 + artifact: Arm64 + + steps: + - name: Checkout main repository code + uses: actions/checkout@v7 + + - name: Checkout dependency repository (xengine) + uses: actions/checkout@v7 + with: + repository: libxengine/libxengine + path: libxengine + + - name: sub module checkout (opensource) + uses: actions/checkout@v7 + with: + repository: libxengine/XEngine_OPenSource + path: XEngine_Source/XEngine_DependLibrary + + - name: install system package + run: | + zypper --non-interactive update -y + zypper install -y gcc gcc-c++ make git jq unzip wget + + - name: Set TERM variable + run: echo "TERM=xterm" >> $GITHUB_ENV + + - name: install xengine library + env: + GH_TOKEN: ${{ github.token }} + run: | + latest_tag=$(curl -s -H "Authorization: Bearer ${{ github.token }}" https://api.github.com/repos/libxengine/libxengine/releases/latest | jq -r .tag_name) + wget https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_OpenSuse_${{ matrix.version }}_${{ matrix.artifact }}.zip + unzip ./XEngine_OpenSuse_${{ matrix.version }}_${{ matrix.artifact }}.zip -d ./XEngine_OpenSuse_${{ matrix.version }}_${{ matrix.artifact }} + cd XEngine_OpenSuse_${{ matrix.version }}_${{ matrix.artifact }} + + chmod 777 * + ./XEngine_LINEnv.sh -i 3 + - name: make + run: | + cd XEngine_Source + make + make FLAGS=InstallAll + make FLAGS=CleanAll + + make RELEASE=1 + make FLAGS=InstallAll + make FLAGS=CleanAll + + - name: test + run: | + cd XEngine_Release + ./XEngine_XStorageApp -t + + - name: Upload folder as artifact with OPensuse + uses: actions/upload-artifact@v7 + with: + name: XEngine_XStorageApp-OPensuse_${{ matrix.version }}_${{ matrix.artifact }} + path: XEngine_Release/ + retention-days: 1 \ No newline at end of file From 8ff7b67c1e8e3bdb927f0cdca89d4b1fb0c599ca Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 12:39:54 +0800 Subject: [PATCH 20/33] modify:change c++ version to 20 for msvc --- .../XEngine_StorageApp/XEngine_StorageApp.vcxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj index 1ca7c084..d6b6068a 100644 --- a/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj +++ b/XEngine_Source/XEngine_StorageApp/XEngine_StorageApp.vcxproj @@ -134,6 +134,7 @@ WIN32;_DEBUG;_CONSOLE;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true 4819 + stdcpp20 Console @@ -151,6 +152,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Console @@ -165,6 +167,7 @@ false _DEBUG;_CONSOLE;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -177,6 +180,7 @@ false _DEBUG;_CONSOLE;_WINDOWS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true + stdcpp20 Console @@ -194,6 +198,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Console @@ -213,6 +218,7 @@ MultiThreaded 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Console From 7f7e6671e882175362dd3646f4f07610dd092feb Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 15:35:48 +0800 Subject: [PATCH 21/33] ci:test --- .github/workflows/suse_build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/suse_build.yml b/.github/workflows/suse_build.yml index 1ec4129a..02dc23b9 100644 --- a/.github/workflows/suse_build.yml +++ b/.github/workflows/suse_build.yml @@ -81,6 +81,7 @@ jobs: - name: test run: | cd XEngine_Release + ldd ./XEngine_XStorageApp ./XEngine_XStorageApp -t - name: Upload folder as artifact with OPensuse From 07f73c21a01b2b7f5be6e56ae493b1d87d416c0b Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 16:07:47 +0800 Subject: [PATCH 22/33] feat: add agentic workflows for issue triage and auto fix --- .github/workflows/auto-code.lock.yml | 995 ++++++++++++++++-------- .github/workflows/auto-code.md | 9 +- .github/workflows/issue-triage.lock.yml | 963 ++++++++++++++++------- .github/workflows/issue-triage.md | 7 +- .github/workflows/suse_build.yml | 1 - 5 files changed, 1363 insertions(+), 612 deletions(-) diff --git a/.github/workflows/auto-code.lock.yml b/.github/workflows/auto-code.lock.yml index 919efe73..529ade0a 100644 --- a/.github/workflows/auto-code.lock.yml +++ b/.github/workflows/auto-code.lock.yml @@ -1,20 +1,21 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"158e10df0b3421742ed672f7a5667899e0977be63eed6a6fa996ccb9de250211","compiler_version":"v0.71.5","agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7a69c89d3529a2cd095dc619f88f20c7fd74f2ece28a2186892a6f72b82a708d","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request","missing_data","missing_tool","noop"]}]} +# This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.5). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -24,37 +25,49 @@ # # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_DEFAULT_OTLP_HEADERS # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # - OPENAI_API_KEY # # Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 +# - github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 # # Container images used: -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23 +# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699 name: "自动处理 Issue" -"on": +on: issues: types: - - labeled + - labeled permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}" + queue: max run-name: "自动处理 Issue" +env: + OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }} + OTEL_SERVICE_NAME: gh-aw.auto-code + OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=%E8%87%AA%E5%8A%A8%E5%A4%84%E7%90%86%20Issue,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot' + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }} + GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]' + GH_AW_OTLP_IF_MISSING: ignore + jobs: activation: needs: pre_activation @@ -63,13 +76,23 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} + oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -77,24 +100,29 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "自动处理 Issue" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-code.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.71.5" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_AGENT_VERSION: "1.0.80" + GH_AW_INFO_CLI_VERSION: "v0.88.7" GH_AW_INFO_WORKFLOW_NAME: "自动处理 Issue" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -104,16 +132,79 @@ jobs: GH_AW_INFO_AWF_VERSION: "" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "" + GH_AW_INFO_AGENT_RUNTIME: "" GH_AW_COMPILED_STRICT: "false" + GH_AW_INFO_FEATURES: '{"dangerously-disable-sandbox-agent":true}' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs')); await main(core, context); + - name: Enforce strict mode policy + if: ${{ vars.GH_AW_POLICY_STRICT == 'true' }} + run: | + echo "::error::GH_AW_POLICY_STRICT=true but this workflow was not compiled in strict mode. Recompile with --strict or strict: true." + exit 1 + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-autocode-${{ github.run_id }} + restore-keys: agentic-workflow-usage-autocode- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs')); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_ID: "auto-code" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs')); + await main(); + - name: Check for OAuth tokens + id: check-oauth-tokens + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh" + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false sparse-checkout: | @@ -121,18 +212,16 @@ jobs: .agents .claude .codex - .crush .gemini - .opencode .pi sparse-checkout-cone-mode: true fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -141,141 +230,116 @@ jobs: GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs')); await main(); - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.71.5" + GH_AW_COMPILED_VERSION: "v0.88.7" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + const { main } = require(path.join(actionsDir, 'check_version_updates.cjs')); await main(); - name: Compute current body text id: sanitized uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + const { main } = require(path.join(actionsDir, 'compute_text.cjs')); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"file\":\"safe_outputs_create_pull_request.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}" + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - # poutine:ignore untrusted_checkout_exec - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" - { - cat << 'GH_AW_PROMPT_de410bcdbc8f0b37_EOF' - - GH_AW_PROMPT_de410bcdbc8f0b37_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_de410bcdbc8f0b37_EOF' - - Tools: add_comment, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_de410bcdbc8f0b37_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_de410bcdbc8f0b37_EOF' - - GH_AW_PROMPT_de410bcdbc8f0b37_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_de410bcdbc8f0b37_EOF' - - The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ - {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_de410bcdbc8f0b37_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_de410bcdbc8f0b37_EOF' - - {{#runtime-import .github/workflows/auto-code.md}} - GH_AW_PROMPT_de410bcdbc8f0b37_EOF - } > "$GH_AW_PROMPT" + GH_AW_PROMPT_CONTENT_0000: "\n" + GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, create_pull_request, missing_tool, missing_data, noop\n" + GH_AW_PROMPT_CONTENT_0002: "\n" + GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n" + GH_AW_PROMPT_CONTENT_0004: "\n" + GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/auto-code.md}}\n" + with: + script: | + const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs'); + await main(core); - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs')); await main(); - name: Substitute placeholders uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - - const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - + + const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs')); + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -285,76 +349,118 @@ jobs: }); - name: Validate prompt placeholders env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Stage prompt files for artifact upload + run: | + mkdir -p /tmp/gh-aw/aw-prompts + cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/ - name: Upload activation artifact - if: success() + if: success() || failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read + timeout-minutes: 60 env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_PR_HEAD_BASE_BRANCH: "" + GH_AW_PR_HEAD_BASE_PR_NUMBER: "" + GH_AW_PR_HEAD_BASE_REF: "" + GH_AW_PR_HEAD_BASE_REPO: "" + GH_AW_PR_HEAD_BASE_SHA: "" + GH_AW_PR_HEAD_REPO: "" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: autocode outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} + max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }} + missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "自动处理 Issue" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-code.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths + env: + GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }} run: | + if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then + echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV" + fi { echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" + - name: Check OTLP telemetry configuration + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Create gh-aw temp directory @@ -363,37 +469,51 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs')); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" env: GH_HOST: github.com + GH_AW_COMPILED_VERSION: v0.88.7 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + GH_AW_GITHUB_MIN_INTEGRITY: 'approved' + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs')); + await determineAutomaticLockdown(github, context, core); - name: Parse integrity filter lists id: parse-guard-vars env: @@ -401,27 +521,42 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - - name: Generate Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23 ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699 + - name: Prepare Safe Outputs Directories run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_982fd4401303461a_EOF' - {"add_comment":{"max":1},"create_pull_request":{"base_branch":"develop","max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_982fd4401303461a_EOF + - name: Generate Safe Outputs Config + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" + GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"create_pull_request\":{\"base_branch\":\"develop\",\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"allowed\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'create_files.cjs')); + await main(); - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -444,9 +579,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -454,6 +598,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -477,6 +631,12 @@ jobs: "sanitize": true, "maxLength": 256 }, + "dependencies": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 256 + }, "draft": { "type": "boolean" }, @@ -490,6 +650,18 @@ jobs: "type": "string", "maxLength": 256 }, + "stack_position": { + "optionalPositiveInteger": true + }, + "stack_root": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, "title": { "required": true, "type": "string", @@ -575,90 +747,68 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs')); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} + GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - + if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then + GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json" + cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}" + export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}" + fi + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="localhost" export MCP_GATEWAY_HOST_DOMAIN="localhost" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY + MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_AGENT_ID}" + export MCP_GATEWAY_AGENT_ID export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro" + export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}" + export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}" + export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}" + export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}" + export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}" + export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}" export DEBUG="*" - + export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' - - mkdir -p /home/runner/.copilot + source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.18' + + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_01a7963a8b9bf043_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_c70074f9d4c4762c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.11.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_FEATURES": "fields_param", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -666,23 +816,50 @@ jobs: "allow-only": { "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, - "min-integrity": "none", + "min-integrity": "approved", "repos": "all", "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} } } }, "safeoutputs": { - "type": "http", - "url": "http://localhost:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}", + "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}", + "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}", + "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}", + "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}", + "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}", + "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}", + "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_SHA": "\${GITHUB_SHA}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { "accept": [ "*" - ] + ], + "sink-visibility": "${GH_AW_SINK_VISIBILITY}" } } } @@ -690,24 +867,32 @@ jobs: "gateway": { "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "agentId": "${MCP_GATEWAY_AGENT_ID}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120, + "opentelemetry": { + "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}", + "traceId": "${GITHUB_AW_OTEL_TRACE_ID}", + "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}" + } } } - GH_AW_MCP_CONFIG_01a7963a8b9bf043_EOF + GH_AW_MCP_CONFIG_c70074f9d4c4762c_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true env: - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }} MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs')); await main(); - name: Clean credentials continue-on-error: true @@ -732,9 +917,11 @@ jobs: # --allow-tool shell(git rm:*) # --allow-tool shell(git status) # --allow-tool shell(git switch:*) + # --allow-tool shell(github:*) # --allow-tool shell(grep) # --allow-tool shell(head) # --allow-tool shell(ls) + # --allow-tool shell(printf) # --allow-tool shell(pwd) # --allow-tool shell(safeoutputs:*) # --allow-tool shell(sort) @@ -743,29 +930,37 @@ jobs: # --allow-tool shell(wc) # --allow-tool shell(yq) # --allow-tool write - timeout-minutes: 20 + timeout-minutes: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" mkdir -p /tmp/ mkdir -p /tmp/gh-aw/ mkdir -p /tmp/gh-aw/agent/ mkdir -p /tmp/gh-aw/sandbox/agent/logs/ - GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool 'shell(cat)' --allow-tool 'shell(date)' --allow-tool 'shell(echo)' --allow-tool 'shell(git add:*)' --allow-tool 'shell(git branch:*)' --allow-tool 'shell(git checkout:*)' --allow-tool 'shell(git commit:*)' --allow-tool 'shell(git merge:*)' --allow-tool 'shell(git rm:*)' --allow-tool 'shell(git status)' --allow-tool 'shell(git switch:*)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(ls)' --allow-tool 'shell(pwd)' --allow-tool 'shell(safeoutputs:*)' --allow-tool 'shell(sort)' --allow-tool 'shell(tail)' --allow-tool 'shell(uniq)' --allow-tool 'shell(wc)' --allow-tool 'shell(yq)' --allow-tool write --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log + GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool 'shell(cat)' --allow-tool 'shell(date)' --allow-tool 'shell(echo)' --allow-tool 'shell(git add:*)' --allow-tool 'shell(git branch:*)' --allow-tool 'shell(git checkout:*)' --allow-tool 'shell(git commit:*)' --allow-tool 'shell(git merge:*)' --allow-tool 'shell(git rm:*)' --allow-tool 'shell(git status)' --allow-tool 'shell(git switch:*)' --allow-tool 'shell(github:*)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(ls)' --allow-tool 'shell(printf)' --allow-tool 'shell(pwd)' --allow-tool 'shell(safeoutputs:*)' --allow-tool 'shell(sort)' --allow-tool 'shell(tail)' --allow-tool 'shell(uniq)' --allow-tool 'shell(wc)' --allow-tool 'shell(yq)' --allow-tool write --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: doubao-seed-2-0-code-preview-260215 - COPILOT_PROVIDER_API_KEY: ${{ secrets.OPENAI_API_KEY }} - COPILOT_PROVIDER_BASE_URL: https://ark.cn-beijing.volces.com/api/v3 + COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_PROVIDER_TYPE: openai - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_PROVIDER_WIRE_API: responses + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.5 + GH_AW_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} + GH_AW_VERSION: v0.88.7 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -775,25 +970,31 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + env: + GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }} + GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs')); + await main(); - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -803,7 +1004,7 @@ jobs: continue-on-error: true env: MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" @@ -812,13 +1013,14 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + const { main } = require(path.join(actionsDir, 'redact_secrets.cjs')); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,OPENAI_API_KEY' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,OPENAI_API_KEY' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -839,25 +1041,30 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs')); await main(); - name: Parse agent logs for step summary if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs')); await main(); - name: Parse MCP Gateway logs for step summary if: always() @@ -865,16 +1072,41 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs')); await main(); + - name: Generate observability summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs')); + await main(core); - name: Write agent output placeholder if missing if: always() run: | if [ ! -f /tmp/gh-aw/agent_output.json ]; then echo '{"items":[]}' > /tmp/gh-aw/agent_output.json fi + # Small dedicated copy of the agent output so safe-output processing + # survives a failed or timed-out upload of the larger agent artifact + - name: Upload agent output fallback artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent-output-fallback + path: | + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/safeoutputs.jsonl + if-no-files-found: ignore - name: Upload agent artifacts if: always() continue-on-error: true @@ -890,8 +1122,9 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/pre-agent-audit.txt - /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/otel.jsonl + /tmp/gh-aw/otlp-export-errors.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -905,16 +1138,20 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || + needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: + actions: read contents: write - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-auto-code" cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -923,21 +1160,24 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "自动处理 Issue" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-code.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + pattern: "{agent,agent-output-fallback}" + merge-multiple: true path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -945,7 +1185,83 @@ jobs: run: | mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + fi + - name: Download Safe Outputs Items Manifest + id: download-safe-outputs-manifest + if: always() + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: safe-outputs-items + merge-multiple: true + path: /tmp/gh-aw/ + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh" + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/evals.jsonl + /tmp/gh-aw/usage/graders/grader_manifest.json + /tmp/gh-aw/usage/graders/grader_results.json + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-autocode-${{ github.run_id }} + restore-keys: agentic-workflow-usage-autocode- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context); + const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs')); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-autocode-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -953,15 +1269,21 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "auto-code" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs')); await main(); - name: Record missing tool id: missing_tool @@ -970,12 +1292,15 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + const { main } = require(path.join(actionsDir, 'missing_tool.cjs')); await main(); - name: Record incomplete id: report_incomplete @@ -984,12 +1309,15 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs')); await main(); - name: Handle agent failure id: handle_agent_failure @@ -998,51 +1326,92 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "auto-code" - GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0" GH_AW_ENGINE_ID: "copilot" GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }} + GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }} + GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }} + GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }} + GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_TIMEOUT_MINUTES: "${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs')); + await main(); + - name: Report failed jobs + id: report_failed_jobs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_REPORT_FAILED_JOBS: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs')); await main(); pre_activation: runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "自动处理 Issue" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-code.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1051,9 +1420,11 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + const { main } = require(path.join(actionsDir, 'check_membership.cjs')); await main(); safe_outputs: @@ -1064,18 +1435,21 @@ jobs: runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/auto-code" GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID: "auto-code" GH_AW_WORKFLOW_NAME: "自动处理 Issue" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/auto-code.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1085,26 +1459,39 @@ jobs: create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} + process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }} + process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }} + process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }} + process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }} + process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }} + process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }} + process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "自动处理 Issue" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-code.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + pattern: "{agent,agent-output-fallback}" + merge-multiple: true path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1112,7 +1499,9 @@ jobs: run: | mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + fi - name: Download patch artifact continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -1121,30 +1510,21 @@ jobs: path: /tmp/gh-aw/ - name: Checkout repository if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: develop + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1155,17 +1535,20 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"create_pull_request\":{\"base_branch\":\"develop\",\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"create_pull_request\":{\"base_branch\":\"develop\",\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"allowed\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs')); await main(); - name: Upload Safe Outputs Items if: always() @@ -1175,5 +1558,5 @@ jobs: path: | /tmp/gh-aw/safe-output-items.jsonl /tmp/gh-aw/temporary-id-map.json + /tmp/gh-aw/safe-output-errors.json if-no-files-found: ignore - diff --git a/.github/workflows/auto-code.md b/.github/workflows/auto-code.md index f73bb475..e8627742 100644 --- a/.github/workflows/auto-code.md +++ b/.github/workflows/auto-code.md @@ -6,16 +6,17 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: "https://ark.cn-beijing.volces.com/api/v3" + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_MODEL: doubao-seed-evolving + COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_TYPE: openai COPILOT_PROVIDER_WIRE_API: responses features: - dangerously-disable-sandbox-agent: "controlled environment for issue triage automation" + dangerously-disable-sandbox-agent: true sandbox: agent: false + strict: false network: @@ -25,7 +26,7 @@ network: tools: github: - min-integrity: none + min-integrity: approved permissions: contents: read diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 4dd05499..34b09673 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,20 +1,21 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"49f1529d95cbf3c8e3d0da1df4b6cee935245532511b98ca9ecd7a44f1d0bb68","compiler_version":"v0.71.5","agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"912d90f58733dc6e64cc594177559b0ccbeb07edb8e6fc58f698209f046ef7d0","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]} +# This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.71.5). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -24,37 +25,49 @@ # # # Secrets used: -# - COPILOT_GITHUB_TOKEN +# - GH_AW_DEFAULT_OTLP_HEADERS # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # - OPENAI_API_KEY # # Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 +# - github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 # # Container images used: -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23 +# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699 name: "Issue 自动分类与回复" -"on": +on: issues: types: - - opened - - reopened + - opened + - reopened permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}" + queue: max run-name: "Issue 自动分类与回复" +env: + OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }} + OTEL_SERVICE_NAME: gh-aw.issue-triage + OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Issue%20%E8%87%AA%E5%8A%A8%E5%88%86%E7%B1%BB%E4%B8%8E%E5%9B%9E%E5%A4%8D,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot' + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }} + GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]' + GH_AW_OTLP_IF_MISSING: ignore + jobs: activation: needs: pre_activation @@ -63,13 +76,23 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} + oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -77,24 +100,29 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.71.5" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_AGENT_VERSION: "1.0.80" + GH_AW_INFO_CLI_VERSION: "v0.88.7" GH_AW_INFO_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -104,16 +132,79 @@ jobs: GH_AW_INFO_AWF_VERSION: "" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "" + GH_AW_INFO_AGENT_RUNTIME: "" GH_AW_COMPILED_STRICT: "false" + GH_AW_INFO_FEATURES: '{"dangerously-disable-sandbox-agent":true}' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs')); await main(core, context); + - name: Enforce strict mode policy + if: ${{ vars.GH_AW_POLICY_STRICT == 'true' }} + run: | + echo "::error::GH_AW_POLICY_STRICT=true but this workflow was not compiled in strict mode. Recompile with --strict or strict: true." + exit 1 + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuetriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs')); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_ID: "issue-triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs')); + await main(); + - name: Check for OAuth tokens + id: check-oauth-tokens + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh" + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false sparse-checkout: | @@ -121,18 +212,16 @@ jobs: .agents .claude .codex - .crush .gemini - .opencode .pi sparse-checkout-cone-mode: true fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -141,138 +230,113 @@ jobs: GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs')); await main(); - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.71.5" + GH_AW_COMPILED_VERSION: "v0.88.7" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + const { main } = require(path.join(actionsDir, 'check_version_updates.cjs')); await main(); - name: Compute current body text id: sanitized uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + const { main } = require(path.join(actionsDir, 'compute_text.cjs')); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}" + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - # poutine:ignore untrusted_checkout_exec - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" - { - cat << 'GH_AW_PROMPT_1857816c8098cba9_EOF' - - GH_AW_PROMPT_1857816c8098cba9_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_1857816c8098cba9_EOF' - - Tools: add_comment, add_labels, missing_tool, missing_data, noop - - GH_AW_PROMPT_1857816c8098cba9_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_1857816c8098cba9_EOF' - - The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ - {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_1857816c8098cba9_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_1857816c8098cba9_EOF' - - {{#runtime-import .github/workflows/issue-triage.md}} - GH_AW_PROMPT_1857816c8098cba9_EOF - } > "$GH_AW_PROMPT" + GH_AW_PROMPT_CONTENT_0000: "\n" + GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, add_labels, missing_tool, missing_data, noop\n" + GH_AW_PROMPT_CONTENT_0002: "\n" + GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n" + GH_AW_PROMPT_CONTENT_0004: "\n" + GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/issue-triage.md}}\n" + with: + script: | + const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs'); + await main(core); - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs')); await main(); - name: Substitute placeholders uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - - const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - + + const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs')); + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -282,76 +346,118 @@ jobs: }); - name: Validate prompt placeholders env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Stage prompt files for artifact upload + run: | + mkdir -p /tmp/gh-aw/aw-prompts + cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/ - name: Upload activation artifact - if: success() + if: success() || failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read + timeout-minutes: 60 env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_PR_HEAD_BASE_BRANCH: "" + GH_AW_PR_HEAD_BASE_PR_NUMBER: "" + GH_AW_PR_HEAD_BASE_REF: "" + GH_AW_PR_HEAD_BASE_REPO: "" + GH_AW_PR_HEAD_BASE_SHA: "" + GH_AW_PR_HEAD_REPO: "" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: issuetriage outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} + max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }} + missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths + env: + GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }} run: | + if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then + echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV" + fi { echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" + - name: Check OTLP telemetry configuration + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Create gh-aw temp directory @@ -360,37 +466,51 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs')); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" env: GH_HOST: github.com + GH_AW_COMPILED_VERSION: v0.88.7 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + GH_AW_GITHUB_MIN_INTEGRITY: 'none' + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs')); + await determineAutomaticLockdown(github, context, core); - name: Parse integrity filter lists id: parse-guard-vars env: @@ -398,27 +518,42 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - - name: Generate Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23 ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699 + - name: Prepare Safe Outputs Directories run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_18efef56cd5f6986_EOF' - {"add_comment":{"max":1},"add_labels":{"allowed":["bug","feature","enhancement","documentation","question","duplicate","invalid"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_18efef56cd5f6986_EOF + - name: Generate Safe Outputs Config + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" + GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"duplicate\",\"invalid\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'create_files.cjs')); + await main(); - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -441,9 +576,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -451,6 +595,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -462,10 +616,7 @@ jobs: }, "labels": { "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "type": "array" }, "repo": { "type": "string", @@ -550,90 +701,68 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs')); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} + GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - + if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then + GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json" + cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}" + export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}" + fi + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="localhost" export MCP_GATEWAY_HOST_DOMAIN="localhost" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY + MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_AGENT_ID}" + export MCP_GATEWAY_AGENT_ID export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro" + export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}" + export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}" + export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}" + export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}" + export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}" + export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}" export DEBUG="*" - + export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' - - mkdir -p /home/runner/.copilot + source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.18' + + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_0138aecd21cfde50_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.11.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_FEATURES": "fields_param", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -648,16 +777,43 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://localhost:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}", + "GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}", + "GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}", + "GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}", + "GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}", + "GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}", + "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}", + "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_SHA": "\${GITHUB_SHA}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { "accept": [ "*" - ] + ], + "sink-visibility": "${GH_AW_SINK_VISIBILITY}" } } } @@ -665,24 +821,32 @@ jobs: "gateway": { "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "agentId": "${MCP_GATEWAY_AGENT_ID}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120, + "opentelemetry": { + "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}", + "traceId": "${GITHUB_AW_OTEL_TRACE_ID}", + "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}" + } } } - GH_AW_MCP_CONFIG_0138aecd21cfde50_EOF + GH_AW_MCP_CONFIG_137b94c134aafdc9_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true env: - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }} MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs')); await main(); - name: Clean credentials continue-on-error: true @@ -696,29 +860,37 @@ jobs: # Copilot CLI tool arguments (sorted): # --allow-tool github # --allow-tool safeoutputs - timeout-minutes: 20 + timeout-minutes: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" mkdir -p /tmp/ mkdir -p /tmp/gh-aw/ mkdir -p /tmp/gh-aw/agent/ mkdir -p /tmp/gh-aw/sandbox/agent/logs/ - GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log + GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: doubao-seed-2-0-code-preview-260215 - COPILOT_PROVIDER_API_KEY: ${{ secrets.OPENAI_API_KEY }} - COPILOT_PROVIDER_BASE_URL: https://ark.cn-beijing.volces.com/api/v3 + COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_PROVIDER_TYPE: openai - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_PROVIDER_WIRE_API: responses + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.5 + GH_AW_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} + GH_AW_VERSION: v0.88.7 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -728,25 +900,31 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + env: + GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }} + GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs')); + await main(); - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -756,7 +934,7 @@ jobs: continue-on-error: true env: MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" @@ -765,13 +943,14 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + const { main } = require(path.join(actionsDir, 'redact_secrets.cjs')); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,OPENAI_API_KEY' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,OPENAI_API_KEY' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -792,25 +971,30 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs')); await main(); - name: Parse agent logs for step summary if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs')); await main(); - name: Parse MCP Gateway logs for step summary if: always() @@ -818,16 +1002,41 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs')); await main(); + - name: Generate observability summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs')); + await main(core); - name: Write agent output placeholder if missing if: always() run: | if [ ! -f /tmp/gh-aw/agent_output.json ]; then echo '{"items":[]}' > /tmp/gh-aw/agent_output.json fi + # Small dedicated copy of the agent output so safe-output processing + # survives a failed or timed-out upload of the larger agent artifact + - name: Upload agent output fallback artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent-output-fallback + path: | + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/safeoutputs.jsonl + if-no-files-found: ignore - name: Upload agent artifacts if: always() continue-on-error: true @@ -843,8 +1052,9 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/pre-agent-audit.txt - /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/otel.jsonl + /tmp/gh-aw/otlp-export-errors.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json if-no-files-found: ignore @@ -856,16 +1066,19 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || + needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: - contents: read - discussions: write + actions: read issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-issue-triage" cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -874,21 +1087,24 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + pattern: "{agent,agent-output-fallback}" + merge-multiple: true path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -896,7 +1112,83 @@ jobs: run: | mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + fi + - name: Download Safe Outputs Items Manifest + id: download-safe-outputs-manifest + if: always() + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: safe-outputs-items + merge-multiple: true + path: /tmp/gh-aw/ + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh" + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/evals.jsonl + /tmp/gh-aw/usage/graders/grader_manifest.json + /tmp/gh-aw/usage/graders/grader_results.json + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuetriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context); + const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs')); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -904,15 +1196,21 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "issue-triage" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs')); await main(); - name: Record missing tool id: missing_tool @@ -921,12 +1219,15 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + const { main } = require(path.join(actionsDir, 'missing_tool.cjs')); await main(); - name: Record incomplete id: report_incomplete @@ -935,12 +1236,15 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs')); await main(); - name: Handle agent failure id: handle_agent_failure @@ -949,49 +1253,90 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "issue-triage" - GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0" GH_AW_ENGINE_ID: "copilot" GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }} + GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }} + GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }} + GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }} + GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_TIMEOUT_MINUTES: "${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs')); + await main(); + - name: Report failed jobs + id: report_failed_jobs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_REPORT_FAILED_JOBS: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs')); await main(); pre_activation: runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1000,9 +1345,11 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + const { main } = require(path.join(actionsDir, 'check_membership.cjs')); await main(); safe_outputs: @@ -1012,19 +1359,21 @@ jobs: if: (!cancelled()) && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: - contents: read - discussions: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage" GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID: "issue-triage" GH_AW_WORKFLOW_NAME: "Issue 自动分类与回复" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1032,26 +1381,39 @@ jobs: comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }} + process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }} + process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }} + process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }} + process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }} + process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }} + process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue 自动分类与回复" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.80" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Mask OTLP telemetry headers + run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + pattern: "{agent,agent-output-fallback}" + merge-multiple: true path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1059,11 +1421,13 @@ jobs: run: | mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + fi - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1074,16 +1438,19 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,ark.cn-beijing.volces.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"duplicate\",\"invalid\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"duplicate\",\"invalid\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs')); await main(); - name: Upload Safe Outputs Items if: always() @@ -1093,5 +1460,5 @@ jobs: path: | /tmp/gh-aw/safe-output-items.jsonl /tmp/gh-aw/temporary-id-map.json + /tmp/gh-aw/safe-output-errors.json if-no-files-found: ignore - diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index ae117408..1fa81aca 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -6,16 +6,17 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: "https://ark.cn-beijing.volces.com/api/v3" + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_MODEL: doubao-seed-evolving + COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_TYPE: openai COPILOT_PROVIDER_WIRE_API: responses features: - dangerously-disable-sandbox-agent: "controlled environment for issue triage automation" + dangerously-disable-sandbox-agent: true sandbox: agent: false + strict: false network: diff --git a/.github/workflows/suse_build.yml b/.github/workflows/suse_build.yml index 02dc23b9..1ec4129a 100644 --- a/.github/workflows/suse_build.yml +++ b/.github/workflows/suse_build.yml @@ -81,7 +81,6 @@ jobs: - name: test run: | cd XEngine_Release - ldd ./XEngine_XStorageApp ./XEngine_XStorageApp -t - name: Upload folder as artifact with OPensuse From 7ab49dda7cae0f9966bf851f2bef44ea7b29dde2 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 16:41:41 +0800 Subject: [PATCH 23/33] ci:delete translate --- .github/workflows/auto-code.lock.yml | 4 ++-- .github/workflows/auto-code.md | 2 +- .github/workflows/issue-translator.yml | 15 --------------- .github/workflows/issue-triage.lock.yml | 4 ++-- .github/workflows/issue-triage.md | 2 +- 5 files changed, 6 insertions(+), 21 deletions(-) delete mode 100644 .github/workflows/issue-translator.yml diff --git a/.github/workflows/auto-code.lock.yml b/.github/workflows/auto-code.lock.yml index 529ade0a..4d412710 100644 --- a/.github/workflows/auto-code.lock.yml +++ b/.github/workflows/auto-code.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7a69c89d3529a2cd095dc619f88f20c7fd74f2ece28a2186892a6f72b82a708d","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f773bd87afa3be78817143d9a6e03d17e476d03da4bed607bd24599a3eb3bf15","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -949,7 +949,7 @@ jobs: env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_PROVIDER_TYPE: openai COPILOT_PROVIDER_WIRE_API: responses diff --git a/.github/workflows/auto-code.md b/.github/workflows/auto-code.md index e8627742..529028db 100644 --- a/.github/workflows/auto-code.md +++ b/.github/workflows/auto-code.md @@ -6,7 +6,7 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_TYPE: openai diff --git a/.github/workflows/issue-translator.yml b/.github/workflows/issue-translator.yml deleted file mode 100644 index d9fdac8f..00000000 --- a/.github/workflows/issue-translator.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Issue Translator -on: - issue_comment: - types: [created] - issues: - types: [opened] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: usthe/issues-translate-action@v2.7 - with: - IS_MODIFY_TITLE: false - CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. \ No newline at end of file diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 34b09673..e3e49534 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"912d90f58733dc6e64cc594177559b0ccbeb07edb8e6fc58f698209f046ef7d0","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6aee9711deaa635dd2eff4345b01c368924bf6d109bc642387f429c761a1c6f1","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -879,7 +879,7 @@ jobs: env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_PROVIDER_TYPE: openai COPILOT_PROVIDER_WIRE_API: responses diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 1fa81aca..c64cb00c 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -6,7 +6,7 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} COPILOT_PROVIDER_TYPE: openai From c139be92da9bec4effa9dec6cef1353cac707d30 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 17:21:49 +0800 Subject: [PATCH 24/33] feat: add agentic workflows for issue triage and auto fix --- .github/workflows/auto-code.lock.yml | 10 +++++----- .github/workflows/auto-code.md | 8 ++++---- .github/workflows/issue-triage.lock.yml | 10 +++++----- .github/workflows/issue-triage.md | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/auto-code.lock.yml b/.github/workflows/auto-code.lock.yml index 4d412710..54fe3e5f 100644 --- a/.github/workflows/auto-code.lock.yml +++ b/.github/workflows/auto-code.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f773bd87afa3be78817143d9a6e03d17e476d03da4bed607bd24599a3eb3bf15","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"23b28a35eed881f99b0993f93aa191082f29f11a3e44fdecfd3f57aeba289a57","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -948,11 +948,11 @@ jobs: GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool 'shell(cat)' --allow-tool 'shell(date)' --allow-tool 'shell(echo)' --allow-tool 'shell(git add:*)' --allow-tool 'shell(git branch:*)' --allow-tool 'shell(git checkout:*)' --allow-tool 'shell(git commit:*)' --allow-tool 'shell(git merge:*)' --allow-tool 'shell(git rm:*)' --allow-tool 'shell(git status)' --allow-tool 'shell(git switch:*)' --allow-tool 'shell(github:*)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(ls)' --allow-tool 'shell(printf)' --allow-tool 'shell(pwd)' --allow-tool 'shell(safeoutputs:*)' --allow-tool 'shell(sort)' --allow-tool 'shell(tail)' --allow-tool 'shell(uniq)' --allow-tool 'shell(wc)' --allow-tool 'shell(yq)' --allow-tool write --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} + COPILOT_MODEL: ${{ vars.MODEL_AI_AGENT_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_AGENT_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_PROVIDER_TYPE: openai - COPILOT_PROVIDER_WIRE_API: responses + COPILOT_PROVIDER_TYPE: ${{ vars.MODEL_AI_AGENT_TYPE }} + COPILOT_PROVIDER_WIRE_API: ${{ vars.MODEL_AI_AGENT_API }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} diff --git a/.github/workflows/auto-code.md b/.github/workflows/auto-code.md index 529028db..e92f1310 100644 --- a/.github/workflows/auto-code.md +++ b/.github/workflows/auto-code.md @@ -6,11 +6,11 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_AGENT_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_TYPE: openai - COPILOT_PROVIDER_WIRE_API: responses + COPILOT_MODEL: ${{ vars.MODEL_AI_AGENT_NAME }} + COPILOT_PROVIDER_TYPE: ${{ vars.MODEL_AI_AGENT_TYPE }} + COPILOT_PROVIDER_WIRE_API: ${{ vars.MODEL_AI_AGENT_API }} features: dangerously-disable-sandbox-agent: true diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index e3e49534..64ab1349 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6aee9711deaa635dd2eff4345b01c368924bf6d109bc642387f429c761a1c6f1","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"8e777d682dd488b88bbed64bee0bd9c7f79a80f4d89429f8b69d85354ec5d762","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -878,11 +878,11 @@ jobs: GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} + COPILOT_MODEL: ${{ vars.MODEL_AI_AGENT_NAME }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_AGENT_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_PROVIDER_TYPE: openai - COPILOT_PROVIDER_WIRE_API: responses + COPILOT_PROVIDER_TYPE: ${{ vars.MODEL_AI_AGENT_TYPE }} + COPILOT_PROVIDER_WIRE_API: ${{ vars.MODEL_AI_AGENT_API }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index c64cb00c..4abe9fae 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -6,11 +6,11 @@ on: engine: id: copilot env: - COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_ADDR }} + COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_AGENT_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} - COPILOT_MODEL: ${{ vars.MODEL_AI_NAME }} - COPILOT_PROVIDER_TYPE: openai - COPILOT_PROVIDER_WIRE_API: responses + COPILOT_MODEL: ${{ vars.MODEL_AI_AGENT_NAME }} + COPILOT_PROVIDER_TYPE: ${{ vars.MODEL_AI_AGENT_TYPE }} + COPILOT_PROVIDER_WIRE_API: ${{ vars.MODEL_AI_AGENT_API }} features: dangerously-disable-sandbox-agent: true From ff0932c78a9ceb5913dd911ecadd803ee407f182 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 17 Sep 2026 17:52:18 +0800 Subject: [PATCH 25/33] feat: add agentic workflows for issue triage and auto fix --- .github/workflows/issue-triage.lock.yml | 21 ++++++++++++++++++--- .github/workflows/issue-triage.md | 7 +++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 64ab1349..a876db61 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"8e777d682dd488b88bbed64bee0bd9c7f79a80f4d89429f8b69d85354ec5d762","body_hash":"b17fdb8754e311cfac38664fbaa575804386c0027689ce0def213670917f77f0","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"af27d3b31edc3b5d16c78e82737fb3d4a89fbff38f4d8b6be28f0184b7d87597","body_hash":"b6960e4f39280aa221d03a942063c639cb664ff5d4c013a58a22fa74fdf17f2d","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -860,6 +860,21 @@ jobs: # Copilot CLI tool arguments (sorted): # --allow-tool github # --allow-tool safeoutputs + # --allow-tool shell(cat) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(github:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(ls) + # --allow-tool shell(printf) + # --allow-tool shell(pwd) + # --allow-tool shell(safeoutputs:*) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) timeout-minutes: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }} run: | set -o pipefail @@ -875,7 +890,7 @@ jobs: mkdir -p /tmp/gh-aw/ mkdir -p /tmp/gh-aw/agent/ mkdir -p /tmp/gh-aw/sandbox/agent/logs/ - GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log + GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool 'shell(cat)' --allow-tool 'shell(date)' --allow-tool 'shell(echo)' --allow-tool 'shell(github:*)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(ls)' --allow-tool 'shell(printf)' --allow-tool 'shell(pwd)' --allow-tool 'shell(safeoutputs:*)' --allow-tool 'shell(sort)' --allow-tool 'shell(tail)' --allow-tool 'shell(uniq)' --allow-tool 'shell(wc)' --allow-tool 'shell(yq)' --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_MODEL: ${{ vars.MODEL_AI_AGENT_NAME }} @@ -885,7 +900,7 @@ jobs: COPILOT_PROVIDER_WIRE_API: ${{ vars.MODEL_AI_AGENT_API }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} - GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_MAX_TURNS: 5 GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 4abe9fae..2c229957 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -5,6 +5,7 @@ on: engine: id: copilot + max-turns: 5 env: COPILOT_PROVIDER_BASE_URL: ${{ vars.MODEL_AI_AGENT_ADDR }} COPILOT_PROVIDER_BEARER_TOKEN: ${{ secrets.OPENAI_API_KEY }} @@ -27,6 +28,7 @@ network: tools: github: min-integrity: none + bash: ["grep", "cat", "head"] permissions: issues: read @@ -53,6 +55,11 @@ safe-outputs: 分析仓库 ${{ github.repository }} 中新开的 Issue。 +## 任务执行限制 +- 根据需求查找相对应的可能关联的代码文件 +- 尽量只读取相关代码文件和文档,不去操作无关代码和文件 +- 尽量减少操作时间和步骤,减少TOKEN和时间消耗 + ## 任务 1. 阅读 Issue 的标题和正文 From 1fb3939f59a7629507ca1d1c1c7a080fd9bc73f3 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 24 Sep 2026 12:59:56 +0800 Subject: [PATCH 26/33] ci:update and safe function modify --- .github/workflows/auto-code.lock.yml | 2 +- .github/workflows/auto-code.md | 4 +-- .github/workflows/fedora_build.yml | 24 +++++++++++++++-- .github/workflows/release.yml | 27 ++++++++++++------- XEngine_Source/XEngine_StorageApp/Makefile | 2 +- .../XEngine_StorageApp/StorageApp_FTP.cpp | 16 +++++------ .../XEngine_StorageApp/StorageApp_Hdr.h | 5 ++-- .../XEngine_StorageApp/StorageApp_Network.cpp | 14 +++++----- 8 files changed, 62 insertions(+), 32 deletions(-) diff --git a/.github/workflows/auto-code.lock.yml b/.github/workflows/auto-code.lock.yml index 54fe3e5f..2069653a 100644 --- a/.github/workflows/auto-code.lock.yml +++ b/.github/workflows/auto-code.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"23b28a35eed881f99b0993f93aa191082f29f11a3e44fdecfd3f57aeba289a57","body_hash":"f4631eca0387739bc44c8ad08d5c0670c98c72198743c2ea9240d97915dca04e","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"23b28a35eed881f99b0993f93aa191082f29f11a3e44fdecfd3f57aeba289a57","body_hash":"79b2f3136ee21d4020cafaeb6080eb7afd5497cedeb721755b22c21725ae7a10","compiler_version":"v0.88.7","engine_base_url_customized":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # diff --git a/.github/workflows/auto-code.md b/.github/workflows/auto-code.md index e92f1310..6f7ae4b4 100644 --- a/.github/workflows/auto-code.md +++ b/.github/workflows/auto-code.md @@ -45,14 +45,14 @@ safe-outputs: # 自动处理 Issue -当 Issue 被打上 `bug` 或 `enhancement` 或 `feature` 标签时触发。其他标签直接退出,不做任何操作。 +当 Issue type 被打上 `bug` 或 `feature` 或 `task` 标签时触发。其他标签直接退出,不做任何操作。 ## 判断任务类型 读取 Issue #${{ github.event.issue.number }} 当前的标签: - 如果包含 `bug` 标签 → 执行【Bug 修复流程】 - 如果包含 `feature` 标签 → 执行【新功能开发流程】 -- 如果包含 `enhancement` 标签 → 执行【功能改进开发流程】 +- 如果包含 `task` 标签 → 执行【功能改进开发流程】 - 其他情况 → 直接退出 ## 任务执行限制 diff --git a/.github/workflows/fedora_build.yml b/.github/workflows/fedora_build.yml index 4e239df7..a09c39b2 100644 --- a/.github/workflows/fedora_build.yml +++ b/.github/workflows/fedora_build.yml @@ -24,13 +24,33 @@ jobs: - arch: amd64 runner: ubuntu-24.04 platform: linux/amd64 - fedora-version: 42 + fedora-version: 44 artifact: x86-64 - arch: amd64 runner: ubuntu-24.04 platform: linux/amd64 - fedora-version: 41 + fedora-version: 43 artifact: x86-64 + - arch: amd64 + runner: ubuntu-24.04 + platform: linux/amd64 + fedora-version: 42 + artifact: x86-64 + - arch: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64 + fedora-version: 44 + artifact: arm64 + - arch: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64 + fedora-version: 43 + artifact: arm64 + - arch: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64 + fedora-version: 42 + artifact: arm64 steps: - name: Checkout main repository code diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f48abc10..0f6559ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,12 +12,12 @@ jobs: steps: # 检出代码并包含标签 - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Download ubuntu build - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: ubuntu_build.yml workflow_conclusion: success @@ -26,7 +26,7 @@ jobs: if_no_artifact_found: fail path: ./XRelease/ - name: Download debian build - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: debian_build.yml workflow_conclusion: success @@ -35,7 +35,7 @@ jobs: if_no_artifact_found: fail path: ./XRelease/ - name: Download fedora build - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: fedora_build.yml workflow_conclusion: success @@ -44,7 +44,7 @@ jobs: if_no_artifact_found: fail path: ./XRelease/ - name: Download Rocky build - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: Rocky_build.yml workflow_conclusion: success @@ -53,7 +53,7 @@ jobs: if_no_artifact_found: fail path: ./XRelease/ - name: Download Alma build - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: Alma_build.yml workflow_conclusion: success @@ -61,8 +61,17 @@ jobs: skip_unpack: true if_no_artifact_found: fail path: ./XRelease/ + - name: Download opensuse build + uses: dawidd6/action-download-artifact@v24 + with: + workflow: suse_build.yml + workflow_conclusion: success + check_artifacts: false + skip_unpack: true + if_no_artifact_found: fail + path: ./XRelease/ - name: Download macbuild - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: macbuild.yml workflow_conclusion: success @@ -71,7 +80,7 @@ jobs: if_no_artifact_found: fail path: ./XRelease/ - name: Download msbuild - uses: dawidd6/action-download-artifact@v20 + uses: dawidd6/action-download-artifact@v24 with: workflow: msbuild.yml workflow_conclusion: success @@ -113,7 +122,7 @@ jobs: echo "$release_notes" > release_notes.txt - name: Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/XEngine_Source/XEngine_StorageApp/Makefile b/XEngine_Source/XEngine_StorageApp/Makefile index da13f047..118796fc 100644 --- a/XEngine_Source/XEngine_StorageApp/Makefile +++ b/XEngine_Source/XEngine_StorageApp/Makefile @@ -3,7 +3,7 @@ PLATVER = LOADHDR = -I ./ LOADSO = -L ../XEngine_DependLibrary/XEngine_Module/jsoncpp -L ../XEngine_DependLibrary/XEngine_Module/tinyxml2 -L ../XEngine_DependLibrary/XEngine_Module/XEngine_InfoReport -L ../XEngine_DependLibrary/XEngine_Module/XEngine_Verification \ -L ../StorageModule_Database -L ../StorageModule_Config -L ../StorageModule_Session -L ../StorageModule_APIHelp -L ../StorageModule_Protocol -L ../StorageModule_BTorrent -LIB = -lXEngine_BaseLib -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -lXEngine_Cryption -lXClient_APIHelp -lNetHelp_APIAddr -lXEngine_SystemApi -lHelpComponents_XLog -lHelpComponents_Packets -lRfcComponents_HttpProtocol -lRfcComponents_FTPProtocol \ +LIB = -lXEngine_BaseLib -lXEngine_BaseSafe -lXEngine_Algorithm -lXEngine_Core -lXEngine_ManagePool -lXEngine_Cryption -lXClient_APIHelp -lNetHelp_APIAddr -lXEngine_SystemApi -lHelpComponents_XLog -lHelpComponents_Packets -lRfcComponents_HttpProtocol -lRfcComponents_FTPProtocol \ -lStorageModule_Database -lStorageModule_Config -lStorageModule_Session -lStorageModule_APIHelp -lStorageModule_Protocol -lStorageModule_BTorrent \ -ljsoncpp -ltinyxml2 -lXEngine_InfoReport -lXEngine_Verification LIBEX = diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 2bb8fb5d..58bfc0af 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -126,7 +126,7 @@ static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileNam Session_FTP_GetRetr(lpszClientAddr.c_str(), &nPos); if (nPos > 0) { - pSt_File = _xtfopen(lpszFileName.c_str(), _X("rb+")); + pSt_File = _xfopenA(lpszFileName.c_str(), _X("rb+"), true); if (NULL == pSt_File) { APIHelp_Port_Free(nPort); @@ -138,7 +138,7 @@ static void XEngine_FTPUPload_Thread(xstring lpszClientAddr, xstring lpszFileNam } else { - pSt_File = _xtfopen(lpszFileName.c_str(), _X("wb")); + pSt_File = _xfopenA(lpszFileName.c_str(), _X("wb")); if (NULL == pSt_File) { APIHelp_Port_Free(nPort); @@ -416,12 +416,6 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,删除文件成功:%s"), lpszClientAddr, tszFileName); } - else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) - { - FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); - XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); - XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,退出成功"), lpszClientAddr); - } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_PWD, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_PWD))) { //列举目录 @@ -572,6 +566,12 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in m_ThreadFTPFiles.detach(); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,列举目录:%s 成功"), lpszClientAddr, tszFilePath); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,退出成功"), lpszClientAddr); + } else { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_502, tszSDBuffer, &nSDLen); diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h index 9906d462..5f7de5a6 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Hdr.h @@ -30,8 +30,8 @@ using namespace std; #include #include #include -#include -#include +#include +#include #include #include #include @@ -142,6 +142,7 @@ extern XENGINE_LBCONFIG st_LoadbalanceCfg; #pragma comment(lib,"Ws2_32.lib") #pragma comment(lib,"Dbghelp.lib") #pragma comment(lib,"XEngine_BaseLib/XEngine_BaseLib.lib") +#pragma comment(lib,"XEngine_BaseLib/XEngine_BaseSafe.lib") #pragma comment(lib,"XEngine_BaseLib/XEngine_Algorithm.lib") #pragma comment(lib,"XEngine_Core/XEngine_Core.lib") #pragma comment(lib,"XEngine_Core/XEngine_ManagePool.lib") diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp index baa561a4..ef07fc65 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_Network.cpp @@ -18,7 +18,7 @@ void XCALLBACK XEngine_Callback_DownloadRecv(LPCXSTR lpszClientAddr, XSOCKET hSo int nSLen = 0; XCHAR* ptszMsgBuffer = NULL; Cryption_Server_RecvMemoryEx(xhDLSsl, lpszClientAddr, &ptszMsgBuffer, &nSLen, lpszRecvMsg, nMsgLen); - if (!HttpProtocol_Server_InserQueueEx(xhDLHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhDLHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("下载客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -27,7 +27,7 @@ void XCALLBACK XEngine_Callback_DownloadRecv(LPCXSTR lpszClientAddr, XSOCKET hSo } else { - if (!HttpProtocol_Server_InserQueueEx(xhDLHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhDLHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("下载客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -60,7 +60,7 @@ void XCALLBACK XEngine_Callback_UPLoaderRecv(LPCXSTR lpszClientAddr, XSOCKET hSo int nSLen = 0; XCHAR* ptszMsgBuffer = NULL; Cryption_Server_RecvMemoryEx(xhUPSsl, lpszClientAddr, &ptszMsgBuffer, &nSLen, lpszRecvMsg, nMsgLen); - if (!HttpProtocol_Server_InserQueueEx(xhUPHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhUPHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("上传客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -69,7 +69,7 @@ void XCALLBACK XEngine_Callback_UPLoaderRecv(LPCXSTR lpszClientAddr, XSOCKET hSo } else { - if (!HttpProtocol_Server_InserQueueEx(xhUPHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhUPHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("上传客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -101,7 +101,7 @@ void XCALLBACK XEngine_Callback_CenterRecv(LPCXSTR lpszClientAddr, XSOCKET hSock int nSLen = 0; XCHAR* ptszMsgBuffer = NULL; Cryption_Server_RecvMemoryEx(xhCHSsl, lpszClientAddr, &ptszMsgBuffer, &nSLen, lpszRecvMsg, nMsgLen); - if (!HttpProtocol_Server_InserQueueEx(xhCenterHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhCenterHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("业务客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -110,7 +110,7 @@ void XCALLBACK XEngine_Callback_CenterRecv(LPCXSTR lpszClientAddr, XSOCKET hSock } else { - if (!HttpProtocol_Server_InserQueueEx(xhCenterHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhCenterHttp, lpszClientAddr, lpszRecvMsg, nMsgLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("业务客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; @@ -142,7 +142,7 @@ void XCALLBACK XEngine_Callback_WebdavRecv(LPCXSTR lpszClientAddr, XSOCKET hSock int nSLen = 0; XCHAR* ptszMsgBuffer = NULL; Cryption_Server_RecvMemoryEx(xhWDSsl, lpszClientAddr, &ptszMsgBuffer, &nSLen, lpszRecvMsg, nMsgLen); - if (!HttpProtocol_Server_InserQueueEx(xhWebdavHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) + if (!HttpProtocol_Server_InsertQueueEx(xhWebdavHttp, lpszClientAddr, ptszMsgBuffer, nSLen)) { XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("WEBDAV客户端:%s,投递数据失败,大小:%d,错误;%lX"), lpszClientAddr, nMsgLen, HttpProtocol_GetLastError()); return; From 02e0bfda337d56eec4f37304a12db2fc8f82adbe Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Thu, 24 Sep 2026 14:41:43 +0800 Subject: [PATCH 27/33] update:vs c++ version to c++20 --- .../StorageModule_APIHelp/StorageModule_APIHelp.vcxproj | 6 ++++++ .../StorageModule_Database/StorageModule_Database.vcxproj | 6 ++++++ .../StorageModule_Protocol/StorageModule_Protocol.vcxproj | 7 ++++++- .../StorageModule_Session/StorageModule_Session.vcxproj | 6 ++++++ XEngine_Source/XEngine_DependLibrary | 2 +- 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj index 3aa5e46d..1e0680ef 100644 --- a/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj +++ b/XEngine_Source/StorageModule_APIHelp/StorageModule_APIHelp.vcxproj @@ -136,6 +136,7 @@ Use pch.h 4819 + stdcpp20 Windows @@ -157,6 +158,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -175,6 +177,7 @@ true Use pch.h + stdcpp20 Windows @@ -191,6 +194,7 @@ true Use pch.h + stdcpp20 Windows @@ -212,6 +216,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -235,6 +240,7 @@ MultiThreaded 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Windows diff --git a/XEngine_Source/StorageModule_Database/StorageModule_Database.vcxproj b/XEngine_Source/StorageModule_Database/StorageModule_Database.vcxproj index 989dd4be..856ed213 100644 --- a/XEngine_Source/StorageModule_Database/StorageModule_Database.vcxproj +++ b/XEngine_Source/StorageModule_Database/StorageModule_Database.vcxproj @@ -135,6 +135,7 @@ true Use pch.h + stdcpp20 Windows @@ -155,6 +156,7 @@ pch.h /utf-8 %(AdditionalOptions) MultiThreadedDLL + stdcpp20 Windows @@ -173,6 +175,7 @@ true Use pch.h + stdcpp20 Windows @@ -189,6 +192,7 @@ true Use pch.h + stdcpp20 Windows @@ -209,6 +213,7 @@ pch.h /utf-8 %(AdditionalOptions) MultiThreadedDLL + stdcpp20 Windows @@ -230,6 +235,7 @@ Use pch.h /utf-8 %(AdditionalOptions) + stdcpp20 Windows diff --git a/XEngine_Source/StorageModule_Protocol/StorageModule_Protocol.vcxproj b/XEngine_Source/StorageModule_Protocol/StorageModule_Protocol.vcxproj index 6490e353..1c0e7f80 100644 --- a/XEngine_Source/StorageModule_Protocol/StorageModule_Protocol.vcxproj +++ b/XEngine_Source/StorageModule_Protocol/StorageModule_Protocol.vcxproj @@ -136,7 +136,7 @@ Use pch.h 4819 - stdcpp17 + stdcpp20 Windows @@ -158,6 +158,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -176,6 +177,7 @@ true Use pch.h + stdcpp20 Windows @@ -192,6 +194,7 @@ true Use pch.h + stdcpp20 Windows @@ -212,6 +215,7 @@ pch.h MultiThreadedDLL /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -234,6 +238,7 @@ pch.h MultiThreaded /utf-8 %(AdditionalOptions) + stdcpp20 Windows diff --git a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj index a1785334..ab895d8d 100644 --- a/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj +++ b/XEngine_Source/StorageModule_Session/StorageModule_Session.vcxproj @@ -136,6 +136,7 @@ Use pch.h 4819 + stdcpp20 Windows @@ -157,6 +158,7 @@ MultiThreadedDLL 4819 /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -175,6 +177,7 @@ true Use pch.h + stdcpp20 Windows @@ -191,6 +194,7 @@ true Use pch.h + stdcpp20 Windows @@ -211,6 +215,7 @@ pch.h MultiThreadedDLL /utf-8 %(AdditionalOptions) + stdcpp20 Windows @@ -233,6 +238,7 @@ pch.h MultiThreaded /utf-8 %(AdditionalOptions) + stdcpp20 Windows diff --git a/XEngine_Source/XEngine_DependLibrary b/XEngine_Source/XEngine_DependLibrary index b0cba1b2..fbb0d6f4 160000 --- a/XEngine_Source/XEngine_DependLibrary +++ b/XEngine_Source/XEngine_DependLibrary @@ -1 +1 @@ -Subproject commit b0cba1b26f00b3c71c42f7f6fdc201d164590cd7 +Subproject commit fbb0d6f4a83d2983c053750a146d663dc1932bc5 From 59d1fec58f33dd00ae6eee771c885a4647077165 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Fri, 25 Sep 2026 16:29:01 +0800 Subject: [PATCH 28/33] ci:update version to last release --- .github/workflows/Alma_build.yml | 6 +++--- .github/workflows/Centos_build.yml | 6 +++--- .github/workflows/Rocky_build.yml | 6 +++--- .github/workflows/codeql.yml | 4 ++-- .github/workflows/debian_build.yml | 6 +++--- .github/workflows/fedora_build.yml | 6 +++--- .github/workflows/macbuild.yml | 4 ++-- .github/workflows/msbuild.yml | 4 ++-- .github/workflows/ubuntu_build.yml | 4 ++-- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/Alma_build.yml b/.github/workflows/Alma_build.yml index d8f12d2a..66603b6d 100644 --- a/.github/workflows/Alma_build.yml +++ b/.github/workflows/Alma_build.yml @@ -44,16 +44,16 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine - name: sub module checkout (opensource) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/XEngine_OPenSource path: XEngine_Source/XEngine_DependLibrary diff --git a/.github/workflows/Centos_build.yml b/.github/workflows/Centos_build.yml index de3c462a..2308490a 100644 --- a/.github/workflows/Centos_build.yml +++ b/.github/workflows/Centos_build.yml @@ -48,16 +48,16 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine - name: sub module checkout (opensource) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/XEngine_OPenSource path: XEngine_Source/XEngine_DependLibrary diff --git a/.github/workflows/Rocky_build.yml b/.github/workflows/Rocky_build.yml index 1d9b0008..5727b28a 100644 --- a/.github/workflows/Rocky_build.yml +++ b/.github/workflows/Rocky_build.yml @@ -44,18 +44,18 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine - name: sub module checkout (opensource) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/XEngine_OPenSource path: XEngine_Source/XEngine_DependLibrary diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c5417f85..f5a2fa41 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine diff --git a/.github/workflows/debian_build.yml b/.github/workflows/debian_build.yml index dd26d828..bf1177dd 100644 --- a/.github/workflows/debian_build.yml +++ b/.github/workflows/debian_build.yml @@ -36,18 +36,18 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine - name: sub module checkout (opensource) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/XEngine_OPenSource path: XEngine_Source/XEngine_DependLibrary diff --git a/.github/workflows/fedora_build.yml b/.github/workflows/fedora_build.yml index a09c39b2..7d03d6ae 100644 --- a/.github/workflows/fedora_build.yml +++ b/.github/workflows/fedora_build.yml @@ -54,16 +54,16 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine - name: sub module checkout (opensource) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/XEngine_OPenSource path: XEngine_Source/XEngine_DependLibrary diff --git a/.github/workflows/macbuild.yml b/.github/workflows/macbuild.yml index 873a9ea4..5183a0a2 100644 --- a/.github/workflows/macbuild.yml +++ b/.github/workflows/macbuild.yml @@ -23,13 +23,13 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' # 检出依赖的xengine仓库到指定的xengine目录 - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index 0437b4e6..a4ecbaf5 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -24,12 +24,12 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine diff --git a/.github/workflows/ubuntu_build.yml b/.github/workflows/ubuntu_build.yml index aab5f1e7..1feafe82 100644 --- a/.github/workflows/ubuntu_build.yml +++ b/.github/workflows/ubuntu_build.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout main repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: 'develop' - name: Checkout dependency repository (xengine) - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libxengine/libxengine path: libxengine From 22cab12b0570ef66e2042b24421908f71a7ee898 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Fri, 25 Sep 2026 16:31:59 +0800 Subject: [PATCH 29/33] ci:use release xengine packet instead git clone --- .github/workflows/codeql.yml | 14 ++++++-------- .github/workflows/macbuild.yml | 13 +++++-------- .github/workflows/msbuild.yml | 18 ++++++++++-------- .github/workflows/ubuntu_build.yml | 12 +++++------- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f5a2fa41..efa3c5fe 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,12 +30,6 @@ jobs: uses: actions/checkout@v7 with: ref: 'develop' - - - name: Checkout dependency repository (xengine) - uses: actions/checkout@v7 - with: - repository: libxengine/libxengine - path: libxengine - name: sub module checkout (opensource) run: | @@ -47,8 +41,12 @@ jobs: - name: Set up Dependency Environment run: | - cd libxengine - chmod +x ./XEngine_LINEnv.sh + latest_tag=$(curl -s https://api.github.com/repos/libxengine/libxengine/releases/latest | jq -r .tag_name) + wget https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_UBuntu_24.04_x86-64.zip + unzip ./XEngine_UBuntu_24.04_x86-64.zip -d ./XEngine_UBuntu_24.04_x86-64 + cd XEngine_UBuntu_24.04_x86-64 + + chmod 777 * sudo ./XEngine_LINEnv.sh -i 3 - name: make pre diff --git a/.github/workflows/macbuild.yml b/.github/workflows/macbuild.yml index 5183a0a2..180a823e 100644 --- a/.github/workflows/macbuild.yml +++ b/.github/workflows/macbuild.yml @@ -26,13 +26,6 @@ jobs: uses: actions/checkout@v7 with: ref: 'develop' - - # 检出依赖的xengine仓库到指定的xengine目录 - - name: Checkout dependency repository (xengine) - uses: actions/checkout@v7 - with: - repository: libxengine/libxengine - path: libxengine - name: sub module checkout (opensource) run: | @@ -45,7 +38,11 @@ jobs: - name: Set up Dependency x86_64 Environment if: matrix.os == 'macos-26-intel' run: | - cd libxengine + latest_tag=$(git ls-remote --tags --sort="v:refname" https://github.com/libxengine/libxengine.git | awk '{print $2}' | sed 's/refs\/tags\///' | tail -n 1) + wget https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_Mac_x86-64.zip + unzip ./XEngine_Mac_x86-64.zip -d ./XEngine_Mac_x86-64 + cd XEngine_Mac_x86-64 + chmod 777 * ./XEngine_LINEnv.sh -i 3 - name: Set up Dependency Arm64 Environment diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index a4ecbaf5..c5bc5bde 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -28,12 +28,6 @@ jobs: with: ref: 'develop' - - name: Checkout dependency repository (xengine) - uses: actions/checkout@v7 - with: - repository: libxengine/libxengine - path: libxengine - - name: sub module checkout (opensource) run: | git submodule init @@ -43,8 +37,16 @@ jobs: - name: Set up Dependency x86_64 Environment if: matrix.platform == 'x64' run: | - echo "XENGINE_INCLUDE=${{ github.workspace }}/libxengine" | Out-File -FilePath $env:GITHUB_ENV -Append - echo "XENGINE_LIB64=${{ github.workspace }}/libxengine/XEngine_Windows" | Out-File -FilePath $env:GITHUB_ENV -Append + $response = Invoke-RestMethod -Uri "https://api.github.com/repos/libxengine/libxengine/releases/latest" -Headers @{"Accept"="application/vnd.github.v3+json"} + $latest_tag = $response.tag_name + Write-Host "Latest Tag: $latest_tag" + + $url = "https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_Windows_x86-64.zip" + Invoke-WebRequest -Uri $url -OutFile "XEngine_Windows_x86-64.zip" + Expand-Archive -Path ./XEngine_Windows_x86-64.zip -DestinationPath ./XEngine_Windows -Force + + echo "XENGINE_INCLUDE=${{ github.workspace }}/XEngine_Windows" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "XENGINE_LIB64=${{ github.workspace }}/XEngine_Windows/XEngine_Windows" | Out-File -FilePath $env:GITHUB_ENV -Append shell: pwsh - name: Set up Dependency x86_32 Environment if: matrix.platform == 'x86' diff --git a/.github/workflows/ubuntu_build.yml b/.github/workflows/ubuntu_build.yml index 1feafe82..8edd086c 100644 --- a/.github/workflows/ubuntu_build.yml +++ b/.github/workflows/ubuntu_build.yml @@ -31,12 +31,6 @@ jobs: with: ref: 'develop' - - name: Checkout dependency repository (xengine) - uses: actions/checkout@v7 - with: - repository: libxengine/libxengine - path: libxengine - - name: sub module checkout (opensource) run: | git submodule init @@ -48,7 +42,11 @@ jobs: - name: Set up Dependency ubuntu24.04 x86-64 Environment if: matrix.os == 'ubuntu-24.04' run: | - cd libxengine + latest_tag=$(curl -s https://api.github.com/repos/libxengine/libxengine/releases/latest | jq -r .tag_name) + wget https://github.com/libxengine/libxengine/releases/download/$latest_tag/XEngine_UBuntu_24.04_x86-64.zip + unzip ./XEngine_UBuntu_24.04_x86-64.zip -d ./XEngine_UBuntu_24.04_x86-64 + cd XEngine_UBuntu_24.04_x86-64 + chmod 777 * sudo ./XEngine_LINEnv.sh -i 3 - name: Set up Dependency ubuntu22.04 x86-64 Environment From a8e3e182c89b87e26823cca5dfcd55e62420ad1c Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sun, 27 Sep 2026 16:11:12 +0800 Subject: [PATCH 30/33] added:rename for ftp support --- .../XEngine_StorageApp/StorageApp_FTP.cpp | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp index 58bfc0af..b530f16e 100644 --- a/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp +++ b/XEngine_Source/XEngine_StorageApp/StorageApp_FTP.cpp @@ -566,6 +566,68 @@ bool XEngine_Task_FTP(LPCXSTR lpszClientAddr, XENGINE_KEYVALUE *pSt_KeyValue, in m_ThreadFTPFiles.detach(); XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,列举目录:%s 成功"), lpszClientAddr, tszFilePath); } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_REFR, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_REFR))) + { + //准备重命名文件 + XCHAR tszFilePath[XPATH_MAX] = {}; + XCHAR tszAlisPath[XPATH_MAX] = {}; + XCHAR tszFileName[XPATH_MAX] = {}; + if (!Session_FTP_Get(lpszClientAddr, NULL, tszFilePath, tszAlisPath)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求修改文件名:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + _xstprintf(tszFileName, _X("%s%s"), tszFilePath, tszAlisPath); + BaseLib_String_FixPath(tszFileName, 1); + + if (tszFileName[_tcsxlen(tszFileName)] != '\\' && tszFileName[_tcsxlen(tszFileName)] != '/') + { + _tcsxcat(tszFileName, _X("\\")); + } + _tcsxcat(tszFileName, pSt_KeyValue->tszStrVlu); + //得到文件属性 + if (0 != _xtaccess(tszFileName, 0)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_550, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求修改文件名:%s 失败,文件不存在或者不可用"), lpszClientAddr, tszFileName); + return false; + } + if (!Session_FTP_Set(lpszClientAddr, tszFileName)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求修改文件名:%s 失败,执行顺序错误"), lpszClientAddr, tszFileName); + return false; + } + + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_350, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,准备重命名文件:%s"), lpszClientAddr, tszFileName); + } + else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_RETO, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_RETO))) + { + XCHAR tszFileOld[XPATH_MAX] = {}; + XCHAR tszFileNew[XPATH_MAX] = {}; + + if (!Session_FTP_Get(lpszClientAddr, tszFileOld)) + { + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_503, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("FTP客户端:%s,请求修改文件名:%s 失败,执行顺序错误"), lpszClientAddr, pSt_KeyValue->tszStrVlu); + return false; + } + BaseLib_String_GetFileAndPath(tszFileOld, tszFileNew); + _tcsxcat(tszFileNew, _X("\\")); + _tcsxcat(tszFileNew, pSt_KeyValue->tszStrVlu); + + _xtrename(tszFileOld, tszFileNew); + FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_250, tszSDBuffer, &nSDLen); + XEngine_Net_SendMsg(lpszClientAddr, tszSDBuffer, nSDLen, nNetType); + XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("FTP客户端:%s,重命名文件:%s -> %s 完成"), lpszClientAddr, tszFileOld, tszFileNew); + } else if (0 == _tcsxnicmp(XENGINE_FTPROTOCOL_QUESTION_QUIT, pSt_KeyValue->tszStrKey, _tcsxlen(XENGINE_FTPROTOCOL_QUESTION_QUIT))) { FTPProtocol_Parse_SendPacketEx(xhFTPContral, XENGINE_FTPROTOCOL_RESPONSE_221, tszSDBuffer, &nSDLen); From 3b8e6e4e7dbb7fac3d02ea65fc51d3ad72eb8227 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sun, 27 Sep 2026 16:11:27 +0800 Subject: [PATCH 31/33] update:CHANGELOG and read me and configure --- CHANGELOG | 12 ++++++++++++ XEngine_Docment/Docment_en.docx | Bin 128516 -> 133079 bytes XEngine_Docment/Docment_zh.docx | Bin 133569 -> 134766 bytes .../XEngine_Config/XEngine_VersionConfig.json | 2 ++ 4 files changed, 14 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 54a2ae3d..3c46d819 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +XEngine_Storage V3.24.0.1001 + +ci:增加了opnesuse系统支持 +增加:ftp协议文件操作支持 +更新:依赖库 +修改:vs编译器使用了c++20 + +ci:add open suse support +added:ftp protocol support +update:depend library +modify:change c++ version to 20 for msvc +====================================================================================== XEngine_Storage V3.23.0.1001 ci:删除了多余的动作 diff --git a/XEngine_Docment/Docment_en.docx b/XEngine_Docment/Docment_en.docx index add094828bed8bf8b9623b1b2072f0aadf387d59..5c0d470dad08a66636af6d0e3d2c69175232a0d6 100644 GIT binary patch delta 114783 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

>*w&fBQyOt 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

BR8x|8W9zKbOw1xb$XqZQ2Z9Ssr%>6ao*JvBXV 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 M;6Pe~hn0i-UzVufGynhq delta 110155 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 diff --git a/XEngine_Docment/Docment_zh.docx b/XEngine_Docment/Docment_zh.docx index 00f5b805bfe18bfc099a4caab1804b571738d494..d2427288f4b2adb9ebf605d803200fb7ae649254 100644 GIT binary patch delta 124127 zcmYhiV{~9m6Ezx}6Wg|JPi)(^abiv;PA100#>BR5+nU(Po#*}Tch~*ZYxU{su14+d z+Eu4F3Faybrj`mCj-2u-?A8thB&aHh1)2b;zy88%*%r|hMw$ZsX5Dst>3T89pOocp zvbiFI$=$A{^v&K`K0zn(`-PsKuycV{z=#WHz+kFjRJK(^-GN<49lbV7Ezw~xTj3|K z;jYIt0)-2A@<_UaGP>?}dTVJ7Pfg9mn)3FFSD^q`3s%0r%56#$2d)v&y(nDV@l&zD z;V@gPY6SGQka2fD0us7x*@hQw=5!GR@QNK1`_+rt&($i8#Z8>a0bekpbI_9XbO; zVnsRVDfbY`j?2xdj9sph)>sXw49vGz+otWo4%?Yml)Wu}Z)^Un(~J(OT`T5a;RrL#(4 zxc#QA->fAHBVv{wD4SE=FbXTDHovH34!n~a45W92C&gy6zPZlqbBB#7fe^64= zRA!#2?CZO32K>k_L9&8`Gjt4%9_4lQBjpIAe>qsmZ&p0K4O_6ne1Ivt2AS&tb%grc zEggS@e7$^+bIp^QyOWlr{XYJD55gH0A{CFuok1iN11RKUg^H<`iJsXPiWSg`5|*#! zJ)w(O!3w2>Fx}syxNrl*+kE^Z&XmIy(Txuya=CKyma@+nU*opsHibJM+y=av5}W+@ z%5bBGHf~PXLJZGLKMxyTL@zmhCh>&a88QW^_|n2OQc2d`tU3u>cvV!*`hBv8-|kZ! zfah_U&iCv(9H}HlIs^O(L?K{-!KBeWCDB^hLe>~gMBgn-W_*EY)l<8rH@}(Lwk^yH zzv@svmW0v#*<#hfM2(5Ucdl3XWcqmTgk^IaUuMo-y?7@Y5D<`>UpFV6Ulg03swnV%^7;eOXOOG&H z;6|EBztr}$`6qw&<9bC2r{1#sJ^Y@gSeldo?r+ID<%9F>r~qUgyQ9j&wQo#1aK??F z^IG;ZrlqTH`J9a+%fAO$)bYoO_b=!TX&wE-bjxHcJ{ka62F;BCRjs2ddOo~myaRfj zEON0q5axv;5!b{#-HZ+aQGRytNX{ST;|lm}V00?qV=+T=8?oTiRnaKqI5HJtOA^Qg za-Kf3#>k|V@@OGVEByZuY)&3Z)ndSu(IG(mG0@Mi>9e|6ohVbYZi{F1$@*(>p9TfD zpEHt0BVPj4&n_*z9h-S<2%|3cEICr~pR;91a+W{VRdHFm465ux6hz%zD>>70gm3YN z;VNPYen%$65yr=R_75;Z>F7m9dJiz!dCEn25AXq*UwJi)G>JKivF4vIa`Q)~oS_S& zk8Y_gJshIE4XYh|TG63DgttUS26HrE+0{s7R_OtdDUxbnMeL(-K_}0}J~l2K#;u#% zDy)B=3FDKza|^s`Sora0+g=F6Wma}tSJ>YauElH~bftt|Z( zsnHvZzah}V9%w6f4CZVwXvgN3cJi<_Mx1a;9pL=E#oge5YwRd5abyd zA}0SxC}Ctw!0i0}LQ8;pVvS6r%U~OB2FGkxkq@B;3)Z0|KB56_Q7>fAp}4N$xRp^= zU8!klk=}ZR>N?5hIyvEvu&l!Zq57vVzO@PP%5$O&7@Kf+ab=j{T)y%=F0aX9)!ig( z(lKhvSUtn8=0K-kJQ8ctIfBG-X`8a+K-FcKYg$ahYRR8V3hYRp`BZ6sP)A~1mY$Ic9(aa(ifoGtt?a;|YlxVBTdw@BpRs`Zttz zfL7}(WJpv_Fl_9ua!c;>(PMVV{R z#!+OeaAKhbq7YH0kv%9Rk~_p}ZPp=wS1DwcBouB`;;>1{B|{13Q$~q{xs8BaY}gpQ zQOc~Bu(MlM@UR@PuL)MZ&H^wQ&p)Mds9 ziCE$nM*O(7tn3;)dd=!e?rlFw#VBkD)G*@I>Iq_MCXv{^$d{{8Ukna-?u1i zyEE8vCm1Ar|F_L2V0gkk?w4(9!^DJp76oB{GQ;3N6qNgs4tqW^O@^h#uT}-S>at_{ zev*Kl?1dGUBmL&Z13~6xdSmaebX)7MOj|;i;?nb#xN5fL=UX^50f5S~T0|)GTtHf@ z3k8!S_@F(Z_QD?eO}GC(IZ^~g8O8*593j|5IG&ic{8b7Gr!qAYZ=6)KX1xPjplrXd>8SR>JqD2bIlo6Alj zeB-SgdXc_$sy8Q){n0snaH&5#zG_8_{wY@r_KUUTuK}ShfyM|W(zuTcVxV<^9;BY)8< z7(t{RcK`3|>=O8U^pstE((>Ii3Vu1$|EIaFHARjJ2jAu4JkUR!LHtL>ZYIaKA zG1e7MJ*&dN`-*;}lwq2sTXY?5@}(2*toj39{bi+s4!N@q~D~hl?Lw zN1ngb7Ua_qyvAZjiBm8|?X_6e&gnlbvjXe;oWTZi96!RliNO^XS4B)%xo4{oJoIch z>4V9EgaQ})(QMzYm;S@Ev66f7Y!XQ2LL;6F1e!hKFnZBVHRETiUq!gMH7NAWi_6lp zRpcIe`~OKM^l5ipOzCfCwd%Ki{mLn2D zRxv7}Kv(<1O%-jt0&AR!C|Q$9os07XmKW%UE#U5TS$QH+fVp3YKv#$iry0+Pc4RE? zhqbzmE?8-Sf{UU4wjVn8Z{W2DH2=apssjWvX_L^txn&&5fj*c#YGHNf%L?8fJNcz2 zn8Q;73B8dHhc)xtqZZEM$i@)k+LqX;&}8JQGj{sJr>76tB%@%%c1AHYGO4icxHcOtV$7nTSXGQ+F|_vm`SgmC}VMBic}9(fe{W~$`r z(rH~|(;9O(H5f6qEGuXzwjQ%Hrejfa zquSKX3qY%K1t#_16G$W!Ng!pPETmDPX}si7#dTpREMdwWR%g2ZMv_RF6rL!waY@3p z=@MV<{kS{v4M!IUL#N-&{I~m)E(W3D^brYtJH*4*l@NS5|F>%<%+1lSAtRF6O<*CwWt?aDr_ckYN-cs@IC>ptBV>xI zIfEKaXoG}2rn|>`6oQHy6*xW_-pl*4E^?#NIJc~pEzcAhl15C9q^P9UgNoL2Cww)@FiK3!3 z4KkdUhxDUoJ2sT!Cp9y(^a+Ku5$x(4$~QBgGcmYtX4*~e2L13P4^$=?n-$XI#6d9R zZVV*8FivW}BmPjG z2NRDEe=G!MA2?<$A5QQeT5o8iR4&oYmik1-FiNu!F$|Ikpa^THtAd<=!5?AE-_S0Y z?U2F1$A>I>P@u87(P+aV#UhM@%+7GBP=AoWw%An;xYOZfI$-Zk`0oOh0Y@ErMoP0{ zNhHaA7&GDC0Xz*f2T(j zP9BTrD_)gAi2sXgtt$vLCJaJ2 z#S_jxC-IeLV8FV#Mkom+YT=0nBWIyLr^k(yWWl-^HBmMd`idPJ-;Q!MM5@eILPgD71=Eyr?r%2*Iv-V;1@V?a1 zS||laRYzERU#}X&Z3mFbnwcXhfNL=XmH*%x;hP(p)(lJvBXm6s4|u()x*2mR-{>~! zW%h0BjuwUhjU}-NI=R*w7kHEwvGH0WaqnMw>2K3zS*P~b^%PYD#IxCd91~szHd11l z=+0vh=zS3BwZ^!>W4Mh-&QlrAwq9ml)fp5PA?J;rbmKu|fU813)7reO^4+3)mf91J zTBFk~1o}PuAZq^Fj&b@kGI&U8Ty|K8p!x7aZe#7LUA3 z-{Z%0g=@v81cqT*cf_+YCokMhd=cA{!WdKpA#@Et;{H8TvxuY60*j2;vLZv!63b*e z2n5##WHKl~f$W_o))ULDyzF5Afu&Ebh6MqWGHtl~&xVEg^{>>7efLT9BBx$~s0KN- zlL-~Hx`jWgd2x0DAFU0}**NEfEKBt8={V=yLmANJq}B$Hs@Zj^-15nRBDM9(hD_Zv zQ=$&Wm9R}HRoBUJFYkY@Hrfy9w;8)zgcTF8kx&OofYkj7Cu&TjRi!3w8Z|_4Y+(+D zM2yE*+z6{RjRs!%{{jHvBUhd=s1ngowSJc=}AZoy!@{kO}LI^1m0)C^+~9 zIU<`B_yz5o`kT+Q*XU zPLU200L{}B+tZNh@fg-r-}WiZw|?E%WI)Pg%@;8^lGsw4i06zaLv}ZqSJP3Y1CLEZ=jmt?iRMSA7t>_dNd(8xh5%EP zwaqVzjxL-_#5cfDpM?5DF14e!{%2t35tika6q-jjsjLj6qzvr9sY%M@mhb^Y)Kg*( z;O*{xlS2fT{k^&)6}b3&6&W=sPI;#v-XP*TAyjNGEC^0#ue4>uaKdoXO%FonqT!KE z!0#NUJg+h2=G#bA!M}$kvV(uHj4*^mB)8rMpK?16V(a`crI^Z2Rko;-<#Xu=KQ64g z)W#HtKK@`~54|yac;ezPX9#5Q#zfjV17~JOa%hZx5qvE>U3RD7gi6qOOd*r z`JAOE%O3&Do=^{bxx>yy!;lJpudoE&SXHT0x5iCLpQr6;pKaeH$a1rDCKfvle0Mr2 z(g0bh{L3XnqGurJQqXR6KQnBC`||<)HOdM?Uz2=_NO9lP0ELV&CW~B(Keh7-c%59- zp}~MENUq%yFRi!2wxGMaK!f!$^rF*e5=&Ik4YN{=aay)19C`F6p*j*Q$uSBdb9T!&!C=@-2^183(&XQfl-W@&|(qaM5mVzJGY0Y@SRVP+b z2N1Id$&5dKyB&1zL58@4n#bg|TeDb2aD9=yPD+*EVbRLgtnCm(6clOsoP1Uo?CvCd zaV=9*>i+o>w)H5;bj|fU@6dC?>~hl(irHx^iQMATT@fPm@gejO{FSf{5CYC7e&c=S z^JSvXWZ>9!4JYa${oA0yZZ0&T!zwF4|lE?be_S? z>UcjBE{LEnOu*Iw6m1=1WkQ7dSC8xOM+LBwzqf_z)OM53&TxDl>z^73EbrMVd?}+v zVlLR5iHA%`LpD^@Oec##tP9q0c1*124Tm)M=PrQYX(R=$t-&p}SfoK`D41Xct(}}A zjP^JnV(~pq>okzwseY3c*GBO)LzCS@h4%>OKGhP0M;jfZ3%*X$^2o{cNbhh>h5c2N zaiTRwh?U5wJCiJdjeLGaR2fG+KLeqo;pdz9lHg86U$DkD*DNL=9`)oigQHI^KI&-6 z9^OLI;~W^tMEtAXPWn4t?gZ6bAJz_7NWWi@!3`uifmTQQ1gK?KPmloxNIj$kHTky$ zf!gWM;AG#sx^^$2H8P{Fo-Qt_rUBb>oV8*sdx<^uY3WNe?GXHpQLGvAJWwK9zTb2b z@K<72HC-z@6>j(f0CqmY)H@S%Sq2weIqAOh+rc~pyg0LevcmDGebrzTf+4%)oA48 zXm^uWMX0u1UY$@hIY^1~u2Nh8Gkm|^C=kBL`JRs(iu7Zl zK6i6BVF|kH&E(O`vgMN~3=TM?&!1B!T!Tw(f^NtFO4YicLY+Uq`d#ZyDsZd%O=ouK z$2m*0k1#-(y;3l*Ath$TN84|sqYt$Bg&Ah}9AXA?RK>XF(a7S^^4vz{EGj*Tr^UjW z-GA_?Eqo6FvL=0l5HVW0@{#1OH7x(3BBE2Y#Y`eTu`QfhX&HuEzC|W)0!JEkBN(aU z2nbq%2@8b+N1pVg$r@`dH_a+eHi3`hX`7L^X0i$iG6$L#7(?YD#V9KX$1Q2Cx&o&a z3$!;ZREg7ydvPDW_OiT8p?SPF_z4t~N(dSfSpO1G#uEgUC>NBm1O^i{7V}3Amp#b3 z%%%QvWa)rH_t0CR!2H@=42xm2(0q;@@q59VOE+cR5^nGaJU_#z2pqf{@!XyOm9Xp| z^)@NN&c%$&6>s)jA%S$-tVad({3o;8V57Fy1+zB6QC;*9iT$7ZGuaB5d{{>nvTXU% z%}Z1OT|_g&J0WEDPa|B2#9%ub^al1CqxZ&yN$>YCM3Zqz=c zLFN^da=-pa2~TvwRZf}^RvrxP_W#GU^;rTYndTewzpa)=MhIpPLbqTLafS^cAPz^# zo`$@icAKXTwU5uX$7~H3a!V^WK09127t=phJGu86tJO4fTAQo6fz}HH?+bdr%)%jM zf1*!Kyl-P9HB$AVW;@ctqBn$+p`p=r6zn_K|50_5`iBdup;l0^oHfy(MHl2a$-vFp zqs!h;3m2;euzQc`=2mpo6aE)k$BuG6T~SdiTyLgaSw=_tRLx~SapCVNdII5zNu>c# zWz%Q&Q#F9D_usNR8;`r)*P{zRbJJDrE~O8TCGQUBz@xt0u(1@3ygKcx~Yp~ zXmu`aA!QN{Mz`~v4PlJ9_k+pHVaS0Sj5yfgqU>U9tvuCC$_T7n@^r&{E#UG1zumC$ zZWr=X4Ot4$p1=iDka-T=MhFb{r0g1jo5`RR@ZQ@3E)1UC4M)m-jf*=zyx27JuK8D@2n~H%Gc=}S6oN3YG%DOvtncq)0*kd z4GUJrT3t&zUd8xq(k2`igGSY^sFWkyY>Fq#F zGcFmfZoG~WGcr;K>{BJGIy-ha0bL3R2|U*Pk#Sa@?r7C zh{)#ZXG7P~)Mn|_L|fW48Ih@D%jkH=V1MW$e|x_?x;O=JjLK;S-cQ2Ip7p+e`Pta9x z%2;FI!ZZP>5u!39wllKbFF;6)>17e7DbOOSg_Ai;s07l`Le-H`u{)a=sAcf7^CRT( z5A4q2MTqzG?)rr2H)I*-Mb7?mj7prxSpz@)(Svd}r-Q6wKa!rO1;(VS?{dPIn7vHD z%c1Wr&t}+AxU6={A+d#6xQ^XT51VDb>L$xap=SzwFUY#V`q9zL#e@xccxhtIXwc>0 z+B>3~7M;UtpyFj9=Vd*d)4H*4`q-axka{j3(=#VBP$0tIOi@}989OlbGI|Mj-GTdQZgV|`&qZe(dLumg#Gmplq^w(*dD zin_k?!gA+q=d-bsDH9rBDEP4V9KI_rph^O`yuyyf=XiA0Kw5U+F-bflCNyB{5E(@` zZ#3THS<2qs5t&8=+vD)lZYv_LN+1+E60~4%HOV!*WfjhZ;)r~?crdY-%R3=`6{RQG?}hryzg zjrKVIkl8H}3{)F1DxFTt0vU&>rob(MH9VUNj9U>%$}1_%(Xc5YTy=xaorwSq?XQj1 z=b_f;G5r>u&j$sUJqSDfe_VEgKdAk|<$?IS%hBj*he9!zP!-=3<7L{`pDqZ>s0jqA z3_)#mh&<=QI74~&jHIaf)XdGNH4MO)1qwMlGzghtb3ssgYJ@WcglQ?#qM=l`9_%tl zD~xQgkvQRGDcTjN>hPdo>9s=psTn##4`c6*&};5NIFtBYnsmoO{a^GD>&RJQu*P-j z_xe;!ZgyD7$eT;fS7&|Kj)AT-6L2QzeeksM)NK8uaJGDaba%>M+u#c|mK@-r34jsT zz`2TVB*Z1T#-+*(07lc>&jr0NT-kY?>tDrvz#?>4=+whKnwv1%ZEE;e>1zZ%h&tSu z3`-{ht_8WTv$jLzFnEuJb=^2-gW#r7F1ZH(E-tcdKdV)>OYc<9=X*#OMw_Nj?27#J zGc8|Ky!YuU;W&cxhtYJZv4H?vSL9%o3Gkq&!W&@-5$EVVs>TvxtyTJeVMT@K0FA1q zfhCZiu~}YkEA7M39i{!HeA&c#RS91uWh@c0N3B z;tnP%x?bBlmat#N@$h=t>1CEh@%nnu(X-R=Y9^H3WmKDp7I89%Gy%yoLT|*^?6VaH zNCWc=i*ED*^&BfID1<0^B)N~;AX+WqzmjvS_$!lCM^Q6~YSjYW*mkSWN3F*U2KD4D zYPS&5!FjH{TN^e!Ph^l_V6&g4&4-~2h5LI#sCQ_-to(1sxu{5-bGdp>g#K}Q5>KzH zL<6MyFmOtkG}tUBO2D)mg3I5jFC~939uoov9Sey_g!-~M2I^z&U-~~(-EahH9B{hk z5^i;FQ_O83Mc==1C4?D~+D&18WgN#f1R0B@Tww@b2Fkig2-5^e2uFhLBBRz5z5RW@ z!e7g$&V$Gw)h2wr@_izL9vDyCY^~@rg9wYSHjrU-5*w9C<^l*ID32BiEFbF(>tNre z@s^)tzD5dF{ZUezvDr7^4fFr<@nq*QFA~MQLCZ9jmF1goE(>$%oS7?!W#J0`qNXi? z;eFb>?j${2;FiVsVaM~DA%qGB<=h)hlXi7LcZr&Uu0uPfYZ*v>f)|K8{ zYg)9K@c;8@aW+Z@D2=$5=6v3C-;W<6HBa zku!_Bfq+9M!^7_gF{hoA_3N&$c9aM*uw?>T|CmJURj_3=jAUC1UnVI=>-&cO{Y;r{ z_!aObdbCnHxo#%|H8;5+yp)I5v27ockvmPbjm8RHI@+tS>l;=^!8HHT7nk21pQZW8 zvzTImV2(GUifZXU79z3TkmQRMxL$o3yui&2c7etmZmjb(L8^gF(6`dZqnrNrDCQue z^Gm(DS7S^RahA;0} z2LSzB#jtdXU+#!ufXJ4~0IyV~#(%F6S9$RSj2iL|rRsFsY-qn-Sy!)C4{pBOOYBh6 zz5W)@iwid~eEdD=EtyyXThnni7)3IJ)m>58QgE?@qM23usbJ1{3`h9p2AUH+6`fq7 zC|pkjnIDWl)r44eF&}nLOVYBt~b^yRxI58n^#9n&}P zn4DDF<09dY%h@3Mbj(yrrvI3hm`S9WOY?_S&ng6QUZ}Zco6(FG@r)uT&b6}-Gv(m8 zSJL&3c*{lR2<5PG>o-UrgDS5P4@mvF$wA$Wk(8d6MzA68IKXk82S8vq z(Pw`u$0PLDvs)vdXmC2*WYc@9PvzL|mQ<@LFzlGAr^1bFNM58F2T@<0w9&V-%kEiEP zMNJPYtq}JyWM|zPsQfyF`#aPa!El$<9;hHz+OdAHBj%vD2Cz1iA<-Glx4ZfF~dy=1_XqOC|8ix9=R~xbl zm+s8WqI+XQk)PsDAJ4}BIjcI$vqWog1-5?I+3~9Xg)sq(fL5I^;{Uueql__V?5y$Gx*|>N&4z`}zHHGmTk}P!>9&YV-fZSS zwj$Q|E^RV%+CWP1s{NWsz(=^Gy=u_`$`|4B73>k^7t4%k-f~)==6^n#Z#S}tBe#nH zh)r~{Yy9n~fhnmv^`47>!R+rgbFdT5d1C4?d5XHnO!Pf(7^wFB9vs0Z3c-DN*+hNK zzQ5_auGf2P>X?53Xr$-E;>AY?*1_(kwa%}Ww%bX@r+62qIK47O>vsU~&eXn}8mv8aSXk1jlw)Lwb+k3mUSZ=xkNCJ&i zzbMvx0_{t12EGgLdv=41PEDE353}Xg8ol=LR>o_p;ZVQ#X6-a_w!2e{>CQv1_FZ${ zF;@hG;v>iyQT~fKT!+7+$1OdaTs{BP40;S2sm%iPJp%LePc3=c{gdLO?L;#!&Xl_k zDh#q~{JxUQ)N{>BYuz!9+|runIeuk28C|B6>U?8YL!)gn9<48xiM9r#*3N=a!;7Y; zh)e$yVln45$H8U|y;K;KfAhYAW6|kHed8~N=S$8iXtc+0-foEM zuNF0+{ftl-J~1_z!Q^CH~mD z+PV*44abLYbh^a@Y3i>GkZdt4v3?7Y%JPy{Lk3N^_a{geX*k4e+7LBqJ^V0fo58GD zaT)KePq%A59N7uVTEiTPACNiIrvbi>Lx$iuKTh!O&nLd}zbtYnl-Q0_d76vJuF(}) znlF3W>|f^XM}^iOd7S2^XJYg5#JD^**>&4aJ&6KA@xi&FoS^Y{_%WFyO;Lu4ukz&V zrBV)qXmJy(_SU%XX~*wGphK{v?q24T%KEIQAM>8fIs%OPS{KM!x&BK^1pb$m_}wOS zvP@oX!sK53`z+u(-TK_qT}&-r#mQ^N)I5y&qdWa8YY2REK}Pd&+AB790zJCucRIdF z5Qu>DdQK>J$mdm-ujA~;#qpVEE~Vuz3_b}nvZ4deaVqWqvJ^MryLnv|D{8tJYd%qU z*jP@Q59zPu3Oa6#TmfgOx{0*(*U$VbDDY?R&ia7vj#qMTS225P0b~e?2+RxE{Pu1Q zgZ6sGfhw0bSKw%jiAW9j81w#Y>8Qh=xY|qf{V3z>zNqwX^!~g(+H~9)gkL-?nHqZV zCkYqn{$HNMJE3MZL#yj_#WCQN^Yt$PRiU?9=QOzGe%}Ykd4JS3+@x%4TE5itc{XFN0co& zQ&;V%fkgrZQ()EjnLE|k%Sa5SfI&a^#1Zz6`VRnwMR3F&%K`k|Vz(xp1wyv7XxdQj z^-&kuOu>0W#^yz+1XPL%N{8j??o-AuaP5jSO91b1v2o0BIWdL`RINf#&&5$6R<9!V zWo*dBB_sBcg0R^FpsCq*!qliG<7h|kt+f(3z_79^H)B7`hww&n$?>?VBOFRcojz*O zB;0CRFyqGL$|9e!yDEHkS zqy)Y#@n}DTwo+W}K?)TP1`V5-7NRoD0rk`|!b#Fh{b79Dx^GzR)CSj=4RyBP-;Cpa zZ9-~g>Um%pT~E;S!7m~=MQuW^G+X+u=m&meIY74e;~nJF!sE{T6ls!wrum_ zcNUgYqiDg^EvA5n!A($x+wVCrk1|ffH~bjMV2NRP1vgv0bpCMn-4GrS!J;IA2Ijwb ziTh2A_ZjOas(H_$V+N~vO(_|4wpP_GCkh`5WlS}*S^C|35HzjcE4w5jMJ~$Ons^)c zD;x0WO!UH*dXJoezw!FXLvo0&JsudA9ue3v_`Y){_S>kB)6o7N{b+2a^gqp<$=my* z1`|Gzf6Y~^&G6i-khpK@xqm_fH%T{W{~p4L%0(Eiq!(yB>R#hP@?R7}jP z{m?|ZuQSEGaFX)3Z9SBczT60ztRZUDF80*T%dnLEqed7>S(i>iQ3-w$1E6OHms1!l zmX2jxwuGzLJ`P%ii8E|sJ$-yGro~rY`;X&>&(71HwAq~Up7y!=TCBfxR?QE~mV?Mw zQI(^=xTtJ-g^h2#7HMPU@_Jf!eCuM$N@Jn(nEPgBxeMK0Giu%S-=!Nnnk*-uz=1cd zylLhVOgqYXTMr|DBtr8D3`Hr`KQLfk$k#P*;!&tw*gu6QV)(9S3-+2gW+VUa+)aD& zW-@YIfl1lsOwe_Vq$leibk1}LRD%w$e&u#LI{w@nx&5Gp`&?P^?I%0@T~C<7jQ4== zDz$=3XnReB_cPT>*tRTFnJYlTF0>XjCPLV>mPHi1wzrm$Y!|>rhJnx+2uQ8IT54{2 zu#(YVTvX9;|M%MjUBcvV*|9PH0lv`*3GZ``khk$9G5mKifSB&UvXi#g{i>`p_`^W!ig5-^?; zNgvWg6p#tB|+d@wkJ{^Ik=2K{A%z>HD)`! za0&)^T{V$>5wS~qb+(EAj0%ZHZB9A%40?f>=gyMo{ZN+?4G$Q2YguK@dK!`1wLp2z zrxBB0O-*I!&<|?fr_^tgki;Tl?BW(&KGxqg&*$*M);_7ax9g8{R~;Qz(l9r0e=+4e ztFJAV2(1C*inO+AbT!xOhK5y@`eohT`x++(8LqY#9V~Qs$mPcxe3nQdHk)V7OmyA8 zkb`$j7p}66DbS6BF_mMA5j2k!%na&aP-Epjm~DDBjOPVGMM|B-sSxtL#kzxAQh22? zjT10Y-yYd*v24?y0%(5g9CaElXfxbx?Xy*y|Lg{S^~B-PV&gikm6b!^~p7nLESgb_&zwgeKVZ$9T$ci^8>7JGO> zi&Z~qym-#@c7yS@@vye<4i_$yN^O;hg*OJU_TzJ!CFwUl;5{~Mm#s=f21SM7Bxf9>8fj>WDM;W+xVSs^{T5MnYAHTNwxdU$ z){F7ci>X`pJ!nN&T|F5(j(ld1SPeJq_oc{r>Z~jlDx*kgYzmjkW_g7V? z;@l5tpA-?opW+_45HoH~v2aL*tFX9Slq+7`%Jc#3NtqufrMCM#+iZs&s3e{{q7IZ_ zk3q10koCaI8p+|-S*bu1M&#N=oc zb+iN7iBAHVZHvdEHN>Wvppt!FXG#+M{Tu5)OMU$7Zr`%)Z`J!3=Oq3i+lnE zEG&zQ;B{cUY7Q#K={?reVDU-XI?@M<+{ zqX^MY1wgZ^!H8V-j$I@$I+^wIn=Xp&qx;u0n1>0i#;42uC%Ugb<;mPe?siw@ z=RO|Po9lb$?_p|o>|_Xi6^5lFy=e4$CWBf{gZd>S`va*62)MR2inBkAB)ZiDgd39v z_#s>yTgbC5eZKoIt9q^6>?_M-QwgY!cE##Nu`)*&2FIT@HthLc;(273id4#}p zb$K0y^f!>SU7dILzKNhCX${2EILNgQC3|?>{uVkG+8;vaDtVB-X>=2?Les0`G4(${V{+;KF?v|s~JEns> zVf(PVf-%gZknrny|Zzy>Z;neaR5MKqxB;1da&uZ+yD`u!;Yl=ocx z(uFU3{d7~qdqd%xA*dyL>e9^tC~^I-(%+_JSgL3ZfS{Ys+7)etj&14YiXXo`cs}|h ze0plDI&A4))&+A0;@*5Ww)g+5d!64FdmC2R?k=`%(H;z7*ZWGMnsV{YTHlY5E%6%d z@6kydjBB}Rz>0->#m)o83MwcaO%&S`PH2pzkmU;0_32bKtCAK6?v$i;qANobhc@Hr zB~&CH0-rlESKGsT)2xgi3ml+u}DwIrMg;}p^5V)7fO%=6;CJ7{si zv2hvHza9*vQz>OqWlN4ewx6A_a+;W3r&(xT={UHAefTy2aq5j@s*bAhw-kXjQ^Wv)IdbTF`)VA^Vc%Zj%uK(wF=j2JXo_|L~@ zXi8%$W}uuT@btW*B^w$(I`Ubk-^s42-z82WGqd@E2~*}{M}mQZ{>NQ3@!-5jYp)2T zw1O1;W-JG2pV|eyxPM0Ma}(YNAg%w+J2c3;`~`uJoDpI7wyu^3y9zhJB4mzl6QsZ{ ztS0Z?`?CSN_muXE9)*FP1{0C_PoAhzYoSBF(cdVk-XW>=d1Sn%%jYugj{ehW%HwMt zOXNcfOIP1DY-Jjhsc9r-b7x3y|4}OnUR@Gi}16I(>as#?1d2Jr94zeJ=PuZr03-_;7VKD6ZwVA1XFsEJpC0rnI<**4Wcg@HYf|o{;YE*!s2$mwsqoUwp-tQq^H-!%=?w9fWvZjp zZSI6Z3iJsGR&Ckox6^Ez706-&BBIQU^tC&ukr@p%auGD~{z*<8qiTZDV}*eq6i+r4 z%6}1XipxpZ+-la}$avJQ@dUiBa4=CCyE6R%l~Y=(j*62oKLYBMn1)u>m$=*9UU0Rw z-pgi5xs1>s^epEm3&%LzUdY0Mq5ZfRzhP42L8EZZ(39&T6Y7#E4(1F}nAIAoB_VU~ zy1sSn%RnAZKM;azschLV&Hd5urrfZS|07uY5~U> zg;G*Q5M2~+=}s4uF(w?{s!Je|j1GRIUD#E~kUSLw$4SlnK18;<8f_+~JGQba zgs{D2Ph|L3O00gEa3N?Ubcz1>)9f0f6p7?cA_gjywO0puK4JrBPL0_}c7~i3a32kJ zDnSGDi^)mN%Pgo)*UwrC8r_Q$Xf(h&z*R~Z7LKL~J6BjqvcwV&BIuh;R(Js2L`t4g zP&ksMXlu8TPoOV#8&$qr(kb9pYZorwamJD-Fe(MXcA<_{_>*TL%AjRa10I>29cQ8v zQ9U%)S5Ff(Lqj)$%Myq^K?i~fC}qUgXIP;5?M+H5CqO&~54w~rl)}dZ|GCxJg0rR| z=j`5qqo*0LPn%qqWseU`dic*eZ%mBkqf*TA~^eEZ_2v9tR1*mxD zXFz~al$Mjkz{tRH{)r!3BuT8y{s&*!etXKbH_B*`4G4=1ER3pH!oSQ?BDsU$c)eU+ z?M~gsd9WE6VWLRm%jP~`we;G4G#xCVqM3wG;KDHAWb`Q{7j#FZ;;yN@ldOr-4HDTX z;!|9-7`JtsC*k)jaDRsY%7Q6m*mKHgK^sng3ujo|p(#q@5)G!IuB}_ji>H2fC0Q!6 zZdRU{swuoln-aFfmoj!c5k?b+RL>Iqe*k7cnZH!Aq6n@X+e}s{7^Vh;n6urI1{$A0 z)Ga|Gp5fIrZLC-Z!eGbta%bWwM89;fW!k|f-n0?HqCniSoXd!H9{faE5~ z(-1fK#1~JQC|UsZFuu#`ZR%E+l{9G*MiMQva4)XCu&;kx{r0QM`g2-0&Ra?wsW%K0 zSisd)%}xlgbRZbPQ-mpFQq+GNa+lpWo#?6yCmz(ik|7Z*1UcOxN)!d1eSm^@W33cS z%ft*5D`Lv=$8GByG8uhW;Ls46*&zEWK=JX~o5%D&^rsZf=yzh4q8_5+@kc}h& zuqpXvucQl_C|Dt)5le@9nifc^2VGYU zWULD!u+(r~m;#VvE`*S)RUl|PLEvTsStX*iEu$US-;@?XFvVUUVhR! z)!sRFY@7k@AO5zRB2j+@(KM69_k-#wnw^4xu?}>XaeKuNWQ%A`rc4t%gHJqmHa?2a zPM@eF&@tZ-07LKnnPyi5Np!X!4G>PZ7GdPd`O%@n#ll-dNE%Yl%sQ(<;t;^20vQGJ z(4P)d1uh{nfk|~A+ogp?>!G9WAG4!-CZDtk@HEULs-I?)1=D|KB1cY6>cyEe#X{cM zUmw2X*mC~LlFaPjh%$^t*9LU-9nDTOEks1Ik9d7hFBS@U?Sz-a9Lvoi zqE|VVkO_(kbdz+%gt2_#RQdGi5QIb)od0Q7BN-M#RG^nSmQ-oSweo$ppam7UrwV*~ z!X-veS|mU>FrWcCH2_xRop@<;%8B=@c``v$83jol8LWTvjPKT9o!BHfr2ahbMIx=W3@RAEBO(fZSSF3PJ35B!02r`Lqx(t|qvHA1W z`s%s8h0uRnL^S;+2|+>BX+Y+BE`-gCuH9L&FD>|fVfE%}^=g<53C7pdqla_`yjeQm z`5fL__Y@~LmCA}xnP}S%2)pM)0_1n(e zDr-MiuYP5(EZNJ??7MTF4^GvkeJ`Q8Y2W$2^KlRW42X4Uq5j}W?cuBH{GxsNUSAN% zzVN)V{;fUt+Mk*I@Ynipk2)VVsEVTZCbEzwm}Vv{D}MtrJ2M#VK3`+_+s#oIzHapQ zV1Iw?zTfWE-@NhfA)3`$-%XK8y50Xf`rf;pk5aw=+I}6*IOq*+e8$;7eNkDv()nmq z5pz}51Sr#~1cAUvl@NU5Iq8j$ycfpO5_E}zNVIgt@UoKnWZLW#1q(;=e?6NomEE8i z)esSxf>cmdf_rVEc!_}hLCjcU&YJM?_LzU0&3QOqK3zPKfJO0MEvAzRDduIvJrom z_Xv4EUP^Q!-U~_hz0EGkl_UumsCp>3jLQ}EiJdFOL^JJ~T-mD^YxfyMo@q*vh@?QV zr4y)#0Fq5-Ng*|`SsGWWfE%WG^d_HxDvK=%{=Zp6v+m*EipQaT1 z3rSelEUzHB2m6aI<|G2ez>*d!is{-McRA8rP@@B*RG30Q6)hcFeYl7w$v-gag0_Ls z4N&xgBwd17^&nz;A~`gt2Q&>;5oOlWeI|A?hX`yRUvLN2a_!Rm8*ZwDYiMc zRJ#|xAd5)m@D>qGe*x)+WKssXDd^9NP_EpwB~#$m+2tj5B&?&h8K zbp9fSw}bY=_4>+6ZRvcZ%qD*raG1fIJah|ZLiLTWHZLsH-mLg_w*KE)PceG`i9k{f z-J*+8^xArE+D#58{Tn&oLUdC^?yT91SD8ebSnQGeFPRvt$BzW7&5}*#?j!r^D|`N8 zUy!D8%e?WRdTy1~eJYix3zCLqKo>S)De7s&F(!=SCxsB*y5)uhi%fr#$w0#maSu8M zLdnoEa?fBIVGtIoXNREY)!xUv(sv!6!@me()PbR z&QBJni>_4o6{0F3q9>uBjFvvv-M9GZ@UZdOX;+@Tk0z({xmj;OcYwDKC5?^iP&cSL>w7UO@40FCcSklRek1FztcFtC#FzxVyG-4sp0=licxvG7s;vxe$r zyYLDBcfHg~2E|cJvN!uw$Z)_rcc=o}+2b)4VBIduvRAHD-rU%0S?=SOlc>83r8>Z~ z$3LpOGg-0>sM7QR;o6#O>x^sb(E8&!BKv?qodG`XV@VniJ+Xi5iQmH3Qh9c&aH`v! z#i~XGwKtsYm;@b}M7<+Q1H{$Y#~zR?my!Y9|5VY;tSxI|)*FLcL2YE0TR|ZLxmJ)1 z82N6is^ex5H-q-a0oQ;yUR>Zu9Y5;$Q8(a6-8;Eb%odycsB2n3KkBl7)V-q~edjIv z`fB}4x8mQ0HRgXOA3yo{$v40!ACm%7e)jRRFS}=-pH=NNf>z2)5Mfyhw}b1L@+l#5dj)QOPhSt~s&f*Kw^oNy zwHt`{7FK{Qjr{#yITdTl+7g^&UV)2XRT zjf2~G^!9(oh0O=+_SFma;@>--eY7?UHka75qfO_$u%F(luKY6tyQu-!@#Q}vwN)y5 z=4tJVm$mh$t;a$?(x(|cgpr6IJd!J%+A|BGe`GL@J{XBU!8KJDiISxge5)G0dUMs+ z-wdH+SU~mq&@qhWWHzYE%>}rucR+u%i~~_Hb<=Bo-CO?vM*9aasUB(X zAF%1$L*4)vA)@d@Hgw-M`H{>5H?*2vzTe-;2EyX{c*+xpyvhPmWF+WyY1 z?@6Dw&S}bC*~O1EAjR1bgc6Uu$2-N}7xv=$+T#TuyN$hcCfOxk4@Xx009|wKEsKAJ z$=3EJFIo~DjVYP3kZyln8h_k_m2N$;ydOq0u7FtnKx;(jQ<; z!blAvqY8=$MP1$Lz}z1@F{*8Ff^k}sIGR^U6u}a-?N$1%ryC#c!q^s`?l~q>L{0aJ z=5`z}pBWt)fk+w#&KDdd5P*@~nW=w&J9)O?wWEJK=l$_7xtTM6beT8vHpLs`)8<6+ zlfrZ{7sQp079DI53mC**SN|(_0#AnAoOVm#;-fIqw4pHHeVm6;%x3z{-gY7kgjtZ29{{BL*~yQgBX;p)AI)o=ReKU_S91q4Y~1{ z{mhwewZ(JoxRPVYl|C6hGdXi=cKEM<9iA-CjGR14pg0NP#0e+@=Z}+$Am)Tg3@0Y? zASa$25k-7r;-q+Dq~!hF$d-Q{Hu9IhD8te)!ox@ag7b&VcpAQo&?Hdl`yl^lQij|aMDR~c z%D-sRv$=lV&e(`7=89l|w~A;6V8bv($Ghs`KDY0^sy&!%Zv56ciNaoX8jAe`vvA6B zjy}|jg_BdKhTW2Ts<-Z51p`$B1A({F7?gwuX z(e#Hpf}%m$Q=Xpwg|jpMU#6x%p59_ZqZ-?Jc4!+oU`tmueqe`5sW@3iJ~tCjYLo3o z7Om1MYp?8;`Re6|^^H5VCs*vnYxebL^^L{)gR7kn&Y*FcJHJZ{(Q}HHHODeG1rxKb zcG^@9h-Sazh%%e7}CXVE9^pUh({&e_%!)M$YdbWJ0c#=0+Le+%64@$3kkhpv#O zzzvNbXV$NpM&L9eC`e^}m)oZT_kMojMDAl(+D6|uHA8>>_tb^1(Kwv?z)>Yh0cPmH zL5hV1({S^Bo7h1JGQ0%nFgqGg`kx)vv8bKzzGHipSRn#7ytTyK?X8LNdvTY^d*d=( zlvPt;mxPtrarK0&)p3=Ra<{$X$|9Gdx;$q;d`#=OdW(prKXjZ4MP_K>r4cbXoclCw zMU`^QlVX1-AX(Lc#vE#(TdJ>o(BYDgkepdw`Mb)#aIU_xwheGg!8%dH07G{9ioIan zU(V*wrhW(570zvbch8=`MZq?V3`Mu(tlEC%gNNQbc6v4+%7#jTZ7f`e+MB=C*2AUB z3C20}Ugu*pq9Cs}YEQ1XHU8f`quJ!L&5 zq7j@PjAJ*mtsgrayTnYQ)eH z>3x5ABpR_@PKwyROL&9@GJEyb^UYho(S}EML1qV{MMPO+hs>Tvc;whr7qU00;~2ow z3`5u5+N5lr4HNHZU7@HCFm5ldGC^v5i&3rwqvCS=-aCsm5sah)9-(LmCHbmJ0D_m{k!_g!|L)pg~*85)MW{= z2A}Jq$hf*nKb@caC=){S<6QNhSE|1}p!DMfAX_rY?pd8AZ~ zsq*rI%dUHmDr<8N$7nW_gfs%#mCtpVUAt7Z9o%vlBl(zwQJ;9@r7jTl=NBB!9HD=l zqdABz)pS&1=mZ52%7Uf@k+n>MEoCC;H=M>NaVGYVpR(Y)(6y!WwO5br`CuEUXeL3?1VBbE z?VFf{WlhmEJ(wsd>VoOzrnE4r@uUY#ibo`ep>8d77Wlc%Nq_6rU#!}{2RnZkMe|cK zO=9A3_Lrz=ie!3_jswQeW1n>arh}OVt^K-ge}9FBjS!29Z1nuTMu_gzq`;)KY;TAY z7KZp%ERoh0_tp{30wOAim^!=TD-B!x=wDNZb?Bxoo@g8fk>2fQ20OuMMvboy|Hsk4 zj8SZOK^9a^)JR5+rFcY{FbjXViENz1&6jJfQbNI4i4cG=bD;3}nmoEIjl#GqjjPE; z7L|Tazq7xfMVJtF=}QAmQNfnN?j%h`jr-C4v^PZW!zzNPDOg9WJJfgaBF1fwcDQ&V z%&P=M8;xk;#a;*2R&KHBnGO#dWiEb zdu>-P4&^=bV`z43zC-2NT%j~GRVqz|;7Fp`4ow8Gtb~Fl8PZ}C$H%+Ti zyYt)Tjojb9@j!5JAM0bBTQy6zQb3Gr41-$+G zkF~{%w4To{-OZqkuuQZr2e2G(W_-5z>1Szg+xW7`z3h10%?fm> z#p$BEmpv6D0Swfw1nwQ){NqXWe8RX9)e90T0IIQlf4B1LVtwOwW$ot_-;_}9=vd%4 zC3tqR_Vkkd`-RGz`?aO>^^KeMo$qVwH>%$!L=5Wry6l(FD{D8a_g_{QR(!+P`8~z= zA&{`*mX3c*cOUsYaf}Q5@;&?OtM%Tp!t>2D=lLjP+1%a@3#m~f!EJioZRD`4_ zef!9w%p$UNd(obMP8(VD7SV?O3P!T1^m&=e)XY?NAq1(Tkj}Y?5y@=R>h@2Nu4myjifH-l|{zqVv)IRr`-)=KrDCB|x_XK}H#d=X3Ug z@ov`kf*T-WwHH_j-{Mtge^Q7GL^6O#%5FH(SDkvXP{>baqHfS(P3MCgVsJ~@=o!E9 zef59ZBVTXQ!`_Jm%CeqeFd$FKaH&ZmHLj{lBsCCNifSpnPkwPPFe6;lpMn4!wp9Yad+K4D5x(>9gT2Jw)lF^bQ3N|8> zC;|c5Z5rSqk7a@}escg1f7P!~iUgx?nR|cdsr6}r;zdri6Ds?TVEP29J^nfa)Tq2( zs;te`?)=t}nU$;6rw{2dw4kOy31+nytP5IaWThdUef@8nbLW_K+U$G?H*M|JHT&g8 z_3Nh;vVsInHmq<#L6&6Y$iHVrRvc%*zJ4pavLa%~WCoCRY&pJb65aG(Vc+zA7y^F= zS2Aw?%*&r>U6x&%-UCs^7F4^P%0Rgm2Ae9C`#e1#Pd!A_o~V+d=>luNynBjr!WfHm zWs{Jaanxtpfx0*OUo=s_S!?jtH76AyFfHK?% zZbE-2o6_Po_{y5JAS7VRQnG5Y$D@DB#QTOXSzVEf)Ut9|b+w3W^OR&hev{;-ATnU0 zp}H*mM1vwg*@f3Fmp*q5QvGyps*tugvv67)2LFxUsjE2<(2*vnSxrHTN1g^dkGh&7 zCTvM>`03|6`&^}FsVFNl($(x%EH~Kn=X~nrA&9Y1t@Hg#}bCQ#!Z z3#p9=AX!j@I_u(_zGb9q6Hb3k=HJa_B}u_6gj-ywJ*LWTG_h&Ou=k}@Uax^xqeD)? z=s+wkgWmjNzxl?#{G{?`nbzp=))LJIq*=r;5xdY~8mGHD83FCaeiKlaK0!oeY~cOj zp=K2``{_gb^1b?(!K(cFvc>o zm%QSs*1Z(XMM1YzqG=(g37Zf=;HA9paFHOOH+9x*;$ra0xciZ$3z*<$#>O|;+>5IS zgH1_HUe7keL)I|{$@G8m+6Q=Zj^+SToMq`s*lopxsDib#X_|-{!2yK`qU073MRQ=! z9kDf}kGb0z&0pvTRu{U6Qd?vg%0NMCsLwJ2GK8WUJi!$VRdJ^wRWr2$PdsWhK58qk zTQjECF(HcX48LpTvLl+_Af3zSXseX7XPe)q#I1Y#{f3{eSpR?L3;Xev`qeM(J9HVy zglM`LuB{=uZpmJYu_kr~M0^i+?xlX%vD0UoL&VIo%tf!cdykrBqKpI!f>3@N)_?$z z6+{+Sbx~~6S3d39E3x3W)AKP736k@?F*~1%<jgmTGra>^H$q z9c{Lyrierf^O%2e%x24g+r$S!9+Lm_%f}Rj_J*(3Q&}vv6vvkpFN@(~E_ZQ>TXis}wJo6wM34L=qGs>!iEb z_ON$h7_k{3^UW~K;?mT<_o}-53+-O;7SV?Oy6hMPCNqC!#EMWu&ackN7(A7esxlu#v$0034|VUK^9|IUToLJ^vMcNSp%VaR|Bax zvcsq$Pqcp;dy7R;SY>U2QBrW5NwPTXH=amO}yM9$40qJ;Yo%@`OZfZ&_m&Zp(l2FZS{?-@Xow7GYWM(nAbsB9=+N zTh?v54)*XO#E}o6s!De2TQX+CZt0g`%3GqLK58j0NH9ok>AY_=q-())<1p!HsTzk2 zDJQUq^=(|(e6U`<`jz8>2Syu-)?KD(2ogJZ3U0*iaab>XQN8uNdh34mn|TWMU?@UM z%4~mQy6oAmG5G^UVgbYskTS8!8rhse1VU0;wl`T>D5*v;_}A~NbB{x#MVPGKBBJR} zh~?ON^LL0T0|VgeCmH@fHhbotrr@XEcI zNW?23LlfzUvV!#g0{{U3|Lk0AQxjPl{woxpcB`f&-MMwF)v8=)tif3ZoZb006-a-B zDMLavjmmrqAQ+GUIw-d&$bhmsBrrOX3z0w5r_=ctdoGOg;b@L8;` zPipAmOn2NLj$9ob2@XIRjtKlHFWlujuXm8Yri5Ovwu{NE-yaS3NBGO2UU@YGmR^CS zOli3p-P$=H!}@*huUEIxZvHMGxfGs@9acJC1=ERA{xwLvHBa*!^CXlHbKqc6V+;N) zzg=y@^cUM{<$Heos%LEA4llOa&CeL|9e$jGqd`QM@(bnlC1qt%Im&>K56siN zVxEL_ybdxskY6@WY9uJ$Zo;w|E92n;&aXIVRV=%wUSMq+x}=lmFnWKL)*tBDt-g*~ ztKAc@yV@1KK9iok0v{B4U-(i~79{m7uPuSZ2Pr!z9dAK}%g-K4g%2RLhq9N{9z0Uo zNQ2_jX58H6cRTzRTN|4|FMm%6i!|~CHE+k+0udgOobqZ0%&avbEjz<`Y#xUDx*g`H zp6>8)xaD!43MOJLE9rlarDEDVH5*<3B?&8dG7pa9VESDX_UCYVy+33sN3pbS-;(9rKRpl)2&Y&kMJF8Z;nxUR!{cF zVt0mv!X2z4cug3=o*fa628W?kf4LPKU~LR*;~W;dLD5Fw(O!Q(8sY^$)Ds-yJum{_ z!K9$aQpj=7FjDjCghe;3zRT0eMmVX+4I133)KtkfYSwA>Io%dJHAmLfY^CdZo@O2T znspd;8hz2*uP_v(uXR;FEqcuV8BWEr0S9Mot>~>yh#o(yQS?sgqNgJKQt@%Akh*{h z@Hq5UU=MiwRttYiE$LLH9L5hH3=5-TH!R$)b=p`w8F8uyGe#$;l3?y-Iliu(JTx!P z?{3}dp@2sZlOVkgmL7xji_*e@d4d!=C?LHpCyq*QGvH*p{IWn|pLw#-Ud$7bXOqfQ zkqGN_jwZRNr0e0_0P!m|B=jwx@!MQp)=oR3x5V;^scU~?B2iC%z6*99l$K^_%gYc6 zskj9eGg4uf@G3t&C>0(MMx|`L^g1ago+V+3E$?ZAMeTu=4)^vnoC(%2SE+L=(_sr0kn!oWsU?9Pr-3_~Q8lQqa?JAv>1jwXL0aBv6_7D=%-zojBP3cNyXO4%fd z)X)-;mecEvp`ay;6@D7!Z^lBcUx^}R*HJ}535;l1YOb+RtpbZY-3dGdEDmmp#yGJ>8}PQ{g16S`gJOKz}v^A=z+Pl6icpcRR+C_LA`YK?&b|&EWQo{pUveKFEFfS>B z9k`A|Q@dI>MNZ{mY?iWf<*B_=@lP1b$&Dmzf6DcTgy66_kZDO`pv$KjgaSEOJ}&F5 z%rr!;@5k;NxBRV_Elh9aWE$juGtY7@q@8~+;WNC=;&yT#pZ79EU)07+?Nzscz+oJ0 zZkCFV<;`6svkjiiOW8+Cv8c8d@)S}S8rf~7wjnDzmCTWRI4P(8fF%v``LiAS%dlI` zkaWDRJbVM@_CW4LezLEMxgvE~qG{_<=o_;U)?ZD^FH&S(Onayv%I{?#lP?%bR$cajt#ox`7qTt4;9Z+!`c{VPmW>w%xV8C2N@t)wP z21UrCCvzaN0>#@F7F{)2F)Kg%53zq}H9FyG3Yv3^9lgQmP`&9IF$3&z^7CEgp0AKG(6*B<{SKzX?Spj>wG9o17fzt{13KlH%PDRQ<)%I_FSLFG3oMK@CA5hXzEM+?xjFOH z`ue)f6CtytQZ`ST_8|EV+68S7H$+0--#HE9I8Q1qZ_0_K7A#0J#@}olA!;@W7FVQ` zmr|k79&C|BvA6{xTU~#^vrtgp@59vbbHcaH%`tW-US$788wC!u*s8Hnz^Jdux%lYM znzyx5n2UsFBu`}IPtSEZL+y z^-kG-tEN3wtha?MRn)_aRN5(?k-mAgqVaSO>Q_t^A(!&zZYlMMHm_a!eM*bNZMFJn zuSBhdXQl~wMx9a}c*eIH82x!$8w2hi>x8QVsv6IYGIQGPtkcWTmRZwpajUFSYIuvA z5o1&_GYJZLC7pjJ@JM!Rm)Dlc@y+&dnN%F?7PS=BJV6T$9NgGKvF3Qa9N&WJD9Aqr zG-~`C|D?cL@@4tui4va&$4`~Nj>;?P@^&+?sd91-ztd%-_Fe_UXn;0<=%4}4^9)I( zy!N2}MI5?ruVs>r!ht*l+?*O;)WDRuS>bv zPwP-y#o4GsyVqf5?M~1CWFeFWx*4C# z`sJ%owAWh4cky;KL&9pQ?^0QTjJNeR>$}>%yha^59gNdrb6<+>2f}rfjr=-25EWMOF{$TOk}mUx(l)q(O$lI9PQtV9jBh-KK_*n!_%u+Ht9+!MG|$S@jqZd1Rx# zSC@YqF2?5aFlV*jF_8iCzdDEkRRNgmdIg}zs~6UF@!;j0$NmM&8r3}Im0dYi+cP3Y zh*>{pyjUGgkC*jwT!5js`qc1vmCSp2b-}=Rk)1U?T*m8mdfg1Qy^jWlOBReM@o58N z#op8OU>#n_rteEmSzRy3C(+_DycqTD@uGhZa!ke7`W4b#z~S(id%2c+1|!laKOpw# zK@K{3n-7L~p_d=z1wJ}}kX@BFq1D+T;J6^%2&rH=7>kL`-B6h#9U1ETRa2-1hlkq; zs5q;-_jP3GW)Sfw#zx?An+;pI5FWY>hr)0c{IfqMim~C-(-D5K>MR)r3_o@p3kX4v z&{>H642_BSr^*kkbC4?4U~6MlI1j}J5I}1KionEb6h8`n=?RMiw-H`e;G75*f9;Qj z#_<ir4=Olks^Jv!`*B0tFXY)xx!tKXf2}Zlf>|hVNI}cR<`H=8%#EC8^Xz zyXx(B`v9&n0Tmx)n~=Qy+MKs!E1;VT@$miIt_Wks6lDraOEahp3y$`7q7)ASkSM_f$|XsW zV3NguYKa&DERIuzLBT`>JjZp)85ay;DfNa&NH0*@H4a_J(INp0?QDQ6fD$MazIF_E zGUNel!Da~ZW`Oh)dhQVAPeuj6s2YG)vp`zafb=krbm!rMc_eR0TF)c-L(*m*X>gp# z@PIRpsa}kkAW-SQ;tU8B?>S#+Fo!CAOS4pe-89n)8VpS7`wT_et0fRWnoL*&!Xljc zQA1&SI~O#p{THpjksen5^`-T|*|=@>c3Xh2DwC@56=Pyoxt|!y38t!y8W1?sab#2` zDQ~+2nM>1)Ndvij7Jv)Mn)?dwjx4o*wYF20h7LGJew{m#Rmwjny3UAf8bs?cM#A5J zdp1glKEd6_vqIAgt~;{%Gyrb^2oN6mvS=8angLK(-G-BnEUSi7WyNsPF0k&%9{5pA z4-n;(5T4)YFc;(AW64NG$uwM(BVc@?f(evu(Nx5EWFu&Zs~)jBU>QSgKQl}`Eocud z@J4z$QBLP-qF&p(Vn}OU6`+brwOdO;QLKTeof3v0=e$w1clFM$^?ANv?5); zmaagn%zzp7K{l6iim#>JoS%9UXJ!)RmT8Ihq4yPN6`{IuO_Z2J0TmOrHko=_xaEyp zd#*yW_NUi|FPiwoh>UXR%)^+_euKJTS`n9(m=d|`cH)u^K9K$?Qy%j?-7iMU; z28SIeZF=P8*vPyq88NC-nwM{xmyhzDd+ImG;AtD2{;T@CDn5IX-XD4R1C#N26a^{s zWu~>0CwCry+j5&Q5QgtN(|2IFcLewt#&)K$C!O3j=>sT27){V&Bw_5guf(~*%>bz{ zguG%u{p@NNwhtGYI%g~u;cVyncb@BDhImBSY3KfaeVT1tM`_3+m~w`9?iDNdVgLQx zHV-k27}r=i2EkOA3$$|+twjicLJ6i&-BE%hS3K5#cZgHKJXA4{alt8KRrDgb|Axm%%HhoYq0c4&q@cXFwXw(W=RXxyR z63D3Pk(Sd)Hy&;^jTH1r>uIEUpR}1q>K!NAKj2(oW)@>EDb(h#JOK*jdnRTEETATD zBqjQPYM9=t#z4Z}Cnz#r4S~+1(S$XiJi_TbswsTuW|D=K|E%%1(!rN(IJ)xEaj-_XO@Y_1#Q^?ue6gxar#=J{**iuEscDQ_T0AY+1(D11IKF5z2h6q)jMOa(ec2JC1Kvb_{jd@1bq{8W=TwUa1w zY)f?a?MI-Khx#fEP+r<@=?5Z zL;K>eUN4`5$N#GRrixFVqn)e$AD0pM0Tl%;^dVyylL~qre@}1IFciS=NPLGX_svP$ zbzQV}LbOG@0UHt=UB_txj&0fQzfZskapR7-?^`kPF?i13vape)B8S9%_WSwy{Y!dz zwM$rULzRvNpE%yB>+~omB8d5F;=H?EoQ$2GHkb#P2~H=@o@(c6`uy4D){g?!18uJT ztt1mCGDi9ce`ylY1nbizCQ4`#no}YYB*HKz6m5kHP~Y|3bR?A^RBPDkCFUEfog7DY z&1iwbTTr&ZAtDi0hVCkc*BWw$F3`9R>URLa3Hx3hHE4|*A!}6)H0%Tdt7@dPZlu2) zZq$v`uanNZkp^|rxEraKPDwq%g`^xNL!lCE;HO%le*~*fYk2}-iA}tWS#0*ebVmgS z$NWtYt-ukE2~zEqiOfn_95)f%fkjZhJ*dE58UcHUM7ED~NO!5+=1)EdnH>b{o^6TF6u$o? z2!_7I3SUF*^oQr;(ZX9esT5wF#!Aog3#2}LV+L<0j_c0c!Nqu9ie@y#Yi8_Bc4hR} ze=2?4m^}mY{0)Y@!y^Y-=|@sXmI9hO$WW!Bgb3$K!3wjNa8NBNV>brg=b3W4N@a`H zLU5yDX%us?fMRW4=osh3e36;HpYv7Bsg;>*=JB28Jm|WQjkm8QCcu)4YN|HWnfCs5 zJus`o`F?)+`s?oQ{@dsK?>~;Fbv(tx6=VF5I4<}8n-8~b!f5j2H@2_yH9J z8CeN@lTLj-e@;r=L?%f|FEg-jegGfmaR!Fp!kI})Ro&J7|JBv7VPMPyl^GfFWn^ST zWF+;6Kc2&&R@iCg$I)}0`b5@ic4WquAGy!<)lz;E>a`5$k);Q5WIxw0c2@u6=fD5E zKa?#8q%|lVWvwzXpX(j~NsAyd)3ZZ8`xN?S8fUQse?FOUNW_lgn>JC#sYTEvMdDVH z#-^QRaH@_T6?#^`!I|gxp;@V3LSY2R5T>W6z&^iVP(KX95+WhI1)@Iy1XoB?Z=sq$ z48;*ht2aQ*hk&3~Z;;rJA$?Jp)ze;)JX2?8fN@C`rk!3AQH+#N>uqvHoOf>IBJuxb4;0#AgoWd}{`juJoD^E7JR z1u!2wYbtXz0}sNw&4Zve?98o_AU%6&3_${BHyu_2;w2}9u>Zv z^R7t`Bm@mMD)BZ7|u@AsY_-YRm_vZq&pT>Feg697m+VCZ1m#A*~0jyiVzfMBg&ndI;^aN(| z&}>rx}>|k==oZH8c_3gtD*c&vk}kNF+i$0IOHkj^bGw#}0-x z;~-A&^RR8P-DMFLfabP4XbjG7n(#28Zg?02np6saokluz1T6$!9a8Aq5z!I*$j$7?VYXa+Wiot6_-T6y5^TQ;tXK{vkt5dSD z2e$8e(2O)oajZylOuc5<9yAp^1~DUwJKL(7`X$c6FzVO=TrO3w=|K>eD!d2;dV&dP zn{OJTjf7M49YI1Hi3V)T$NNi)e?5_H^KG=Yr1j9NcE+<@&>LAdX(e&yqoaKAP*+Z4 zc$6o)YgfX0;JeXhWMMmn_PrBu^CA5O*7~Qg)|eCeDdei5D+k}?7`_SBHlf}kOA7*Z z*WPv<+XTIeH^6H{{t9gm&CPK57EE&5Q*^G!L09J3UX^_E+a?)BdX$_IRCzk=$*yk5)GpUiMoQbCHt z43#BS11@2aV8u}{!!f6~&Q2PlUx@gXXXt1He%1QKi+BRVx^Kr}4K<$YDclmgEWMay zpklqVAaWGTeh}bEf~W?Ie;$ezcos`!ao}5M6nD_-?M{%^aO@i{jd_ARRAQ^5u{bSN zQ#{zK-U||e=D|L8Upa1}{8i~Nw;kvMaIMt@U(I{ZE_le(E&I2o#(;?1fzv;*ZvfxM zu3&Eb*!tM9AHpd0{)X^F0?!On-a|O_;o0&nfZ%23ivMn;$~35Hf5g@WMo1c0VU;DO zjFY&Yff+ntFcBsvfHIT=Gu3lFzP0K#n4zxUq9cCj;&2outrVrT?3C*%JP3nf1XHI- zF$@Z1++kRrM)CZ5#CSatIQHk8>0ssWLpG z4Bb4w6h)mp196B9@W^CkS$={epUmj(6_9Eo`@INNWrlA;f0c?BN+vi#|LKeZ?oSy- z((u|I!$|%%6;!T(To|tZcBZs+115wV*uMm}16pJPe(~_CTRcmCYQk=bjN4*)@skL9 zVHQ8#WuKq{{3{F)J!n-Y*Y}2>?#6E+yMM`roZdIT192bx3gG$#iv;`JdK3=76i=vt zd#ZM&75bJHe^m2e^}jKHwaRY{R04^eB7IuWBVMz=Cc~(JX$h4l4l-*R<5C5n0D(b; zyanb>iX+M2<2Tv_*Eypr%5#0y%erkTmhdyQROVPUUp+na+7Cw83DurRrM5!-j?@_} z^N}RatSdRy5NYrazdK96NU+^4VYffOm>2PAG_BkWf7kRR@_^B!XWEMBD$+&f$%Eht z-mp1x4RWl5>6C06YqQ@!r`xnE17PY21`H$3AkN&ZLzUS^5IMeC$U;8kEq&;LxHsi` z$G)Gs<22vurDEm8QX0tnX|U%DX|HySp?{swY|&wT&cuL?J*Co=mMV6Cw<)L7MMSIp zao#V6fAo@Us}s>%^$k`vHHONwzIhhUWMdDGO0l8#Jufv#5_G9;kGEabsQ|@h<1}@+ z>#{jDCtH2eH4a33GtCWRNiWrSK5|I0Ja3&|f3Qkib+^&B>y0J(r=??CThi+E#BMs8 zj^>LJ15RcV;Sk3z-kdfz?(nub(RAD2y09zFe}-sd)W_z~tBXO-i`|7~jLikC6)Nvt zdm08T{Mosi7=<-bNZlI8;Uzh(hx3&_-gMM1unkdP5PZinlyov23=YGsYo7Z>NgE)E zg|XKr3^#K6NjA@|?e^fr+Oy^f){j@k7*E&xZQ1TfAqKq4U`zV@{`Arqm+jo!EdBG+b9Z_pqkO6> zCMQ?WnSQ=r_Vlj8jj19X!@MJRrk-W341GwPw$k*(1;Hh&Y&aE9yUlpO#cRK}2<3U2 zNHP)9)`DV+aWfruM)T2X6Dqnel%mk0f5OqZw0FjpDi8Knki}$}&J-|G=woF=u`Qp@ zi{W8&AqW?Y7ck1R!K%SftIc-N-}N?HIk@sZYq0K8om<+#Di54JAWCOA%dcRzm$wUH zGw=DEo{j-W4k7ZUT|}ZdqTL>mYT%krXc}>d3EE*=_f2a*N zSyZorqd}X`c30m%(3{H+18x=#`*6q{CIH@a6?7I$dK{#|F=sQaHy`J1)?8gJFFmcb zJnD{*b4{G>fijW~ZF0wuxjS|(#Z5x343-$M&ui@A%68i)a13@#6suB`yA;7?$9by0 zkI05hhNI?coi>_l!xZ~=dood_e?_qsqCu$!G)L|9NqkUO#PWsp3X_226*Y;p8 zt=ygKTef;xEP&x`q(yWvz{V>HIqrzDF58MJ*xqop3)917zt=bV9N0{_I2We^0}^5~ zBSLq*=>!RBt1heL8++9s*`cG&Ri`T~-RXR7^pB0>&_6WjbSQxRDK91?e^+NBs+0C7N$zpTi-qg+m=+|Cxvr7b%? zyEODoZk%TPV0mQr^GkOdpRdN0PWyHhdV9`3Wh>_$iMEek+vX@R21@ZZ_B&$?DED^) ziRdG(4d0X3LX!yns=-l_PvuooynnpWEq1fHv+e8U**`4(#)0e?$sp_QeCYbsrjWY} z?WD9&a^1bNpyMXz@#h&q6rH`CuKWcz&vsLXin|go9C`z((bzJgG8vDLw9Nx;?kY{W z>nn$n;H%_t;b`Ei``vl49?M7NI@xU2TvNX8{mhcJ5(rc;>Yu@|w5)Ve=zs1YjT^Kx zEzZZ%NDe1fYL*wR;Z9O#Q6+}~u^TVrFxCV5kc>lpm*#_DH2~p8A-q~y7 z!j!a&(miw*9ye!oy-_Tp=A03-RdQmE5v>qGFQE>!T27+tq8}~tLw~!20hfxSjWvRk zuE~ny#>`qw=JR2)kGkNvT^hSGH?^~)Yw1x4CC%P2c}S`g<0d_b%YYCFN>G`=6s!8Sp0hj>|hS50z-U=-=7!CUBeejRe{}? z_}#MYQL$A|8#1BCe=NBwFXn|sL(R6k-0oA^H<#NnDRXb5t|Yh!oOz&ru_?o{ch@dZ zLX$I#B7cLfgl3*qRWA8^eBUoxRTW$-Z{PK(Pc)n0f*5~|{2kBn6i?OuiQ*{cU+T5c zhxLBo!=LB6P_G?gKf2vc@JI#ehp;e5MKdg)fe+9q8i-ry+hDCgUt^x@=;L0!re_JP zO;Yp+9MTZoMXPI4^)(gWo6r^QelM$fZ@-43Ph_QEe|Y_|kF8}Z&?EQGe)XO@)7Rf% zp&Yss!aJF6qPtjXXLee^`)CcZd`E*+hWcd?Q_oD_U!sYjq4YzX4x}jakv6uT0V(tK z*Mq$q4UKz<@tcxnhF5YUw|&d6mhq#@&aRUT zYJ_q|x`bKa^GxdZYVi5zf2=9JdA}#Cb46Mr))$`tJ0{C)^gk4^zh||-rEur|7J~gZ zJ;IKx7iT~R7JfS*{rumT5%>WWm(~RW5VO^ck_8HB5`IW}MF9Y6bhB}iS_OY+>tyP6 z{;E7v7p&*qcF`N&ZEsCuy7o4j4@SdZB0e9e%ExusZJ$D8B#0S!rr`=7&6dAJj6Lva zFk>B_(FPr!3BDeX7zU3zUZpUfMzh>t#5u=Y5+x4`BIzoJQKzdNW}2>kkbFsu%G8^T zpFFnYw5yEx5}6KCESV`jONf8)sIzf{X*9cX*q~sL ziM-n-gu!Dg5`4_{dROA_RI}y>EAB55DLP)!n&RO7scBS3+7%{CvF2K2+1va)XuuY7 zK71z>b9h_4Dsxsh+V2x`psH@_#ndZg^4NN)g?FyU+IZ)B3Z5yxr|N&08+%#@{uz>E zJc40{XX+hp9klbb64@tY@ajyd5?)zABrdj*jFZj9^KpN!GmUD{p0$~ADH$GtH!_77ahBzQkTrOwJ~V%LCN{PcjZdC! zWg#grB2GB0wd@eb>n56FJ5i>xV=W^jMSeyW3R`I=TG?Zagv|D+kZyU@GLZ(qP1^M0 zQ74-_)4|beGs%sHTyK)3GVf_ho%NiMS$MazTY=}3qi37X6hD7in@Ril!Xv1*E~cI8 z+F**WJ|B3Y1~Dolv9MoaYVw)i)}ojWkuIX=HIP1K4QRS;Y*UyX-C^B&>V-!zTpB!r z;mUPDLMTjCBl^n7)H3kfri3Na^}Iw0jM!m#4W=sxUYOo2UW4gpIy@6*V0pca@6093 zngcH^(KYYsN`-%;5j&$2P*O-Dnufb=uNWfA9U+4)d~2>cP##Dv9VpS-m>hDWwW%{V zT6blpUMuzEdDPCQ$z$`ju^hGDx5BIAQ?|SYGI!X=5YpM^vq4Jugbeo4rLk&5inlQi z_DgKqj_3g?4QbRSE4Pgi=U8vs9nVX>sJ>7+GnHV1zX%w}7e=WO<_@akmo=>d`F z%2bu(vP?TUl>^1gQs#J~kW_iCi)?RR>k^wsnYa=5Ty2pOW~5#*IClbYu|P<)G)<+YG-8bQnl*(>dQ zkQCkYz%+l_o#CjV?Gi~mpYA7HX;xo3%yb-AbcDuHDLaxKygF0=W9k(Wc?9#C2N^P@ zGnh}O>jyraad@83x_shwTbJZABPJe3u*5%y@3?~xXjf|NXSwUj9Q|_gT?cZBkiaoW zQI2u^j4}Sg{c*-#Y^^&JyG0sx);PAsoVD`O83})eX~$WqMt;VbIr$kE>%_Ho-Uf~q zIq&+yJ#{{$$lnRF*LkL*oWt0H%PdD-kXwy1QAl9#P)KgPbMjm(@3wr+6XFfa9T(*u z^6JENh|oG&4jct_IknE;Ddv#-5Mo)+37H4#Ey1(L@2bd`*PvR{k%NVFjzPLAONd8M zeJp>xIyF?z&$${8zNV|GOrB43ne8{%d@v^@vbEt_s3#;=)(ftsaM;ssJ6oNu`*PUR zjqnh2GpEHlz zgvm7OO}aRH>8)KIciY<-Jg>El#Ut&6on^rh2PgmjKQ z37MSV4mLLSB!ast=a7Yr#-o<=%sxhtL&r3_5sq61?;G=v97@A+F(HSHjWm5@OGvJ! zKdeD;lPe%Xh!^YA5Rz~FwrJeNyAaLf{C0@Wa*RuI;!tU^CCK6t$PH#qD`Ya)hvY8y z-h~vlRJ8=2BUvGZL(Ds**ja!7gnUJgdI*{9FNAD5`;8&H!QMy6F>^#yG!lb-&WnVP&*6hoGIQOE!Ro~>20=mLX*+SRMjMVnXa2zY;}evKIJuN@sN24@+yGHFb7$+%wRh{$O>5ew&erULA^nzq+&VmyGRvJ9 zHrp{`1J@BpnehjTk5@hHT-=rjBl{slQnHoTI;BH?VI!<%gj9~h#vD3JmzdM!`s-NC zIP6ccjxDZOPT7iBeB<~|Ea6aXBBXN_i2QxaaZ4eA>o$Zmw!D91mC?!TR;{Zi%Ta$Zx2KkUM=*)r`6+qf<* z4qpyhW_a0yC&Ygq#PJ?xwa;jll+rNh{go6Pk|i_KP= zkj{D`CHa3JylyEbuH8@i58FE_U$gQWiF2+(a%V}=z2ho_)PpT7 ztE92mrxkMI`XSq&_hP!YKEsigHyo=GlGvY1 zD^&KgvRvnd^AWQ6BtU+b;XWXVqsRHu_TY-nz8Wg&cgQY$fD*4Qv|B+j$oo@~E8_4kbl)=He_*cJ^XBL`Y!{&dxdJk}Ms} z)q#K6yTn|Qee`^OA^T)GhA$)>D8!2&d2He8Y_rNpNS2z}s;|^IdVz0w|qO%;u z$uiUU#C1-7awa9G9jQPh+k@4a%VqgAcrMnmtjZ;Z`IKM;R%$gt-j1Dp3$KJ8)D!yQiE=dmlSjtI|Z z_D+hTdz$X<1xZn0O8r!NP(o^fy=nLN`W515y{Hh!CYP4)qW z9O|C(@(1mlS2*NYgtWtMuXKpJ2#I_;s_aq+oR5&f(xqIcbFFTtxq{=!+-`s8 z!1F2Fu0iT8`;av5wvf#JLNV4dpB5wApT}mx;tHa&g(Z)`+R64#vCeSE0wI}CM+s>x z$%|N<^GrevS9Azz%vr^T!e{P^GnK2ggbeOaQDk?sq$twctm`+z{z7rSu}3MSbESE4 z$@2;FA}fHiW5vhLnTO)#((-Og*B^h@AozHgPY8y+!xGYUm}zv4PX?7Jp5w8}+F`%3 z><|WXNeYMajU}bZ(y~bQ^Et4x4LDwdyc-4_kjy#Cl0#){qvUvq0G6Cm=dl$VIT|5k zvPE3Vu0LZWM6Rq||Eb5o_6#<{lAyya-bM%Y6ZG7plx#Y4VcNa47@kb2N1{c;D2?@~ZKwr*VJ747z_ zJnEe<;b&yo@d>^%Z07UzLI!*9Wyr7QnNDjsvR!0XH6Hc!`Sgrm>>-!Ak2NzAEl1hP zx;ocEmSx;QZ!bSN4qJDFB3yqrA&F(8kj1qgb)VPA5!B0hwr+(Sx-He+6b*S{hwM1s zTkWZqT1e&Y1J#~#$W;~+$GEEXo~r|^GjanN5$*80x!^e?U0rl+k5+FA+lfLtTdG0^ zS94cy7uQBeyj{$5LMGE&4VPoPSIFUfa1Hh#`GjPSyVQ^zS0xL{99e%5a`2rc;45b$ z=K+J&~YqE!W*s4LZ$6u5DZM0CNN)9L9s;mt@OIov=9FeKnAI@jh94q%>sySVp zO%gJ>maP`!_)JQbTfk>4YH^wCd#n7`16`Le4y#k*p{qu%rwDDWCbLwo)dtq}wR*>= zC2Ng`s+QWW%vprmE?Ca`3aQ+k zRmj1HZ7yckSA*B?@x5?;~^{G_JJAX5+R6t+5Bjfa0;6GA3e;I($0IJlY0x$4%^ zp<7Jrm*ebZ>&Elxo7U~cb*imL!#=H$eBhs+2FFL+kp0H1Gj>3nk$rZUkzfsvZJLWK z{o3?_JJ?pg)FxD zx=k9z0f~HqygPg7s?nW0oKfp8oL1Dui}|#>T@!_foGGrM?LI`Vxanb=R*V?W@>&kd z;CkgAWQsBE$irZ7SID6$vBhp8O%?ju%? zZ3sE(thtZ>Gh!Y0p6SyKjy?4mJo|t`CZF{cvJPuK#q~VXbp@`-?aPdVo?E_hbic27 zhz<6YDzCw~tY>K{6}9z)+E;3!^)H3w^<*Uw}%aftm2RaVR{pC@Ct@amSayt%8b37;mn~+{BU-$4jyiw zlt9E^2U~ggQ;pg1a%B$B#KY}KBbdlKcm$vLOxFm(9%v_oc^Gv_hUMspJ~P(~8C-vd zKVn>mna1SU9v$gAoPZtMP-WUl?vM)|DKw4}jD6^VDL$pc{TL%(md_@QY%Istg_Hv? zWO4A}8E@Dt5OTPWW#l+nV~m^(SKtZBT!%7>J!Eh!g~HXSLQcHU z#M?~0{kk~nKDr-V5jA=^bo(1UMz(v$5C`+RO&8cA9y3+0+8gqZ4?MSAVM}OUCUYI+ zyqsbGbY9VLo@vVJ<^D)Q7UxT*xhz-0Oydeu{FI1i3sUCW#c}d!qhTL?Iy-;3Zg4u; ze8`Q{#lexeePosSN66un_VnS<2};NzYB+;iT*W(sd_aC%NQEICRvTX+wx)DO;YY{9ukj&TK1Z$7Xhc<8L#cx(?4A97{eSm2HYC8{jb0 zav7G1v)Dtm)+~OqS3OHS+ys9zt4Z90NXX&zz^wL=i4+nJd|F-Rt_?y?a!0y^^{J4` zR_Cn2vd1!Ost3AmSLA$&kj7c7*^FgRZ#H+hfps=N4_wkh;_7rE_KNOlen60E?2ov@LayI<)8rsmWk`~hZ}#_?uLkKu#CCbpIgQ`=B#DnA%D88 zajdh1Os-^J)+M$agbbF-%lbnMeY)dhOEV!!v*OP?NPJD>>@1d{}(mdHHy8tW!wfTfdf1k@cLA!!2ej?90y> zF@rg4$=&8Ii7SNYd2Ety9Cukk4nOl;NQZmjR+J~foE1Z1IlAPDbMA2My9am#yAj3# zagjT}2sxa>TFD-N?0BYSpZ-4} zlWmH%Pbu*JRND@`P~|T0>Qvv^u9CZQxNmCJl({3=YVL3!%aVP-XP1|(0QMVKONmd` zt$w+~F7zvLEI`QN4zbmp;|QRT%oe1Oa@g%23TIM;9Bvj~Js!G@uAUladDgfNb%SeI z&soJad^>RC@ST6-Z)=3iF*_lJJJ|?1oTyqOdX7r1xi2PQ#3asvt?2@1W7qT-cfS#G z@JrT=k>ept>Y1ahLgHa{mK|*ON@019sjihQpJEg;@!W^GjLFFL`{vw?=grK%Xb7ub zjcl}m{*Sq0;=9VsCC>=8rff8Op#^D-O%1D`jgd`(m2rQ@>^M1by^xeKmS#*0T>GU} zyCQn7LLzsT-@UZi$1q0pjylVjF0+%U*O-|u^kvMx^ARPiXBl8obrEzVq0wT*^c#g$*m$=7(*<~B(y&IqRCCZHWjJHl2 zU-s<-h{%7h$C?roWLcuWV1h8rL~f*G6x0NFC9Lz_{apD>u?fL+mIrdO_#P;;PoH8{ zvWa37JRP;KLtYpyGW%9ijN+KsF0QFJv4_gL5T`HbiJQ30WIo+}%&=F#kifmqO!7XV zmJyONo|`gBE6OyQQnD;vQt=)$ktS`63->gQLkxf4WD__?Y4TR}NTa)t3`TWL4w0)s zB={oVP-t?w_|C1}5};t@)Z|@Lf@_egYuGW=`!jW?*5D`TBL!{ zPBgnz7yVOH*2VDyA=33YpKAQVE^kJQ6Y(UpsnPJ+K2ww9JA+IWAz;){NUCW2o4P_l z3(|kcg27fL=WrZdRf`=I>7-L__3ds`I})wy^5<}^-R?1f6yjFOSf6P975E-P>vsbn{Epw7jce_r z%S`b;hKyCO)+aZxs$WQIn9H%g`+|R`r|rJmi|N46%V9SK>z9kDv+O>9gtos}!E+tH z4cQLt2Pq_Q7fZPjq6FmAunwm(H!?8NXruF-<*{*_@yNk3hJxKyth`gsJlNe$o8Ojp zT_--1jKA<^I=gQ_#Vn7Ekc;me77_~PjHJzdLY-xEKKMU2Hz^vV-aN%w_)LFsVlI59 zNK~K)W=pV%DZXUKXOo2N``-{LwxNY$i~MbtqorRl^4la`&>pqbrNMMicg_gEQ`5zF z#BXF`=G)fn9Oi?C$g<Q7>bomUN9%Ik%duRAt_>f)OLSG*0eqD z<0^>A_Gi1Qi|bih_I@=yStrDE7Qv2fdE?HFa5ePwthSn~>Ff|MV(xt-+@pP8DV6Z_ zjU7YoCVpq`Ht4uNQgaR?=yuBV$LyE}7AE#7*=4dEPqZ^Qhqe1M-(-Jb<66&Qx%zCI zZw~AE>?&ztbl>hw7sk@;>L#*;UEM9La}ZHq4t>Cl1lH(l#EP=YU9!lLdWD3AwK!s5 zpRlrY_bK*x{>Qy*kVIp1SC8C*&Kap~Jd5CrYz?fXb|we5YMdz-_p1?NJyMmE%iquk zlu2&5)?Ip5tAJ}DZF+wlkSy;EN9|l_Smowq$2*kqZr4L02TS>QkEcJKE2*Qsx9ND8gt_6O&7~9Bu1o3+4HEIADDR%A`#E4(`70mRmCAK!=ql(GW^W4 z9sMkqB*n0wkdqJ_m`Ra+;tXnZDPYTWY5OW6i~7{168K(Um#X7az_Ou3ME}&~>rfl+ zvOV-p7vengY4(49p>64MlxswmgAm7k(1nD>+$eFxRq!sa;IaRkEBj7Cq@9vd#>|~7 z^4iei5)v0{+G;|=IINJwnJ!mFaoAN*Hr17ws;Z9x`whC9Eq9b~yZ0G-hfcpPtZD0v ze5y=dghU9gPLm_fRMmoM*y(Po<_q)nuI1#gipsSrACP~)QzhYW7g|l+H^Q}+5@yR? z>pozv&GpMo^ygf+u3?86A%0-Q%yk#}hIS{n@n8k?ybCH2Y1Xo&=rc~{l6{tx_35x7 zvLqjVA693?PS!H}L?ZTpaHEFPGc`J_PjvEfJVwlf#Dozhi9P8ilww0Gjq!txbnIv6 z#`GS23?YA^qF?XE5)0uPOzC>8Aac{hgD#P0G%-@*cB_8uXXj?UES{@&`}6>wT5_|y z&bI$t(v2u7=JJ*4ZCyTKuOlHLV?QA$uPvkZ?v@N5tiP7~EF@!JE#`y|96k~_4g^0vc;8C);t z-QkM*ofJpEUdSOfC?xP{R_SbGQ1W@xWyTY|Z6u&a>CFlSy>~B9kMG#=)<|uWwDeZ? zE!He}Nu7!^#LLA9*ug|DQ(Wk4dfO6rUy~F)tm(}1dxZPq6XX?DmNY~y)%XgJ| zmt23qD1qK}am9_7Jc=lh;xo>F=FqFGZyC~f0EBLNhBNY#4aRGv;w>zS3zq~ zh`UhwZzN&m??wvx=t4Z7@{tXFWsGeI34F3nLi9?^jq*>c!BK~NS*&jIp`+SU!2buR!N+vwq(+j^|i>l;pvi!hC;}yNf!@$LzVrS#l~Y^cQ?=reL>=jd0f! zA;DsOypLm-86Yx4$4H61B2&>)^~sunC!vL;jP+t#Gs|@-`!22veL$b0R4tA=Wxvp4 z5fUs$zkDiJL~Gs4Z;V)T>a%1LR;UQ^8MBH)!iBXSKF^Dzc|s)laIK3u?P-w_%`$(! zSf5zemzXNnANW!X*OPmR2|lIZrONmQFBw(lLJLxe&3LzpuU3XCj=F~3+Ypf=eTGQ8 z0-s;-EgWvr5fUoahxj(kL<`@yr7gnw^k59uIryG-3ll_2;;KsDd+1DweO(=+Y(h+C z$=8?Vc%mN~G}^sFd_zB2szmM~QpkVUx1r$JcK!!_T0chO+f@BnK4bPp$l)f$jZBO< z`ziB{?W#F!Z$i9gk zkzp?E${A#hRy+&08{iG3-hDL^*CPcZ{=o=fFw+LKL!=e#C^#S6Tt`1RI0=7$v?+q@ zM30e@;EF3~rG=fXK)*4#mI9tU333lDdPc$TDi9pChMZCV2szXV1@8`b=LYXh#O&Da z6S>er5h7wFE(v0L?tLaC6wF!$Ki!2^jZD77J?!!)uYoc|h~u;6lGe$gZVX|VfZADz zoftvisXl{TFGT-EWSe}LUkHCOgo#<-pv`gUfrZ#z#<+ioD`oUTWp(?^tCO7b3+?v<5@o zRlGRUyb^b#4@G7oYO^5gi6gXO-v^dS(}q$_MK3gzE`tZ>Q+nk4PeXt0wqh1RNc7n8 zFw}7wtm6`5xu0riK6H#ZgqEyB?=ZBo@1bBs{weOlMh?%^z~{t6n{MFA)X<}}4DM-f zS41CB)3lx?(%7X5xH`kieMSnAiykpgyOVQ~<>-(k$ao$-%#k9m&RnkGd8V-B(_*AVh&BQ> z#&XVL+%l}@8tb{W(m+WbFL{mFYfp&htVonARWLUdjkO7@TB6zI+_XQ@$mry;8OiO-d=$++mj6#CLdoQC8yLCAt(ecUQxYN#o^eIlGp*{knR4LlmX}TzN+&l8OJsykzk+;7~ZF* zD<)%1$mDD;j6qsUnVGjmtOzFz1TxY3nXR6r8A|zLgL0Zf@#~NB2u}N?R!#>vIvN0mqNQlU2Mowz4|aGZu_rA3lVwZShHXsdtr(mjWEXX}mr_?efvu0{vD@B|_u?Z;O*MVt?x=hVy^H$psN_otoK7Dk-K z1nY+y19ryOnTMQ_He0bH`-)jBJFl_IsNGZ-X8-NhkE6#zQpS!^yHRI{??7CcXSkS% zC1(}wdHBMNh~A=gd87Wg(O>8%;#qj7WsI%cyWW5K4i>`Q)BdNlK&R2atb3bwqL3fc zgrX?=qT;TQ$~8a!dCnLU@^2JFn<&HvR@Vw~&Omu&>ybf*(m3k`3u$Z7yY^2mCGL1n zsb`nXtpR{@iSu&Ei=G1F%4I?sv zusVd-P{QTkwtx7i-lFjwSKXpCfRStieI|cza%M1-;&SH-W_g78!mO1y&mY#9d7C1* zxDTS%o^4fIdJVjS(|GENH(id{Kj(7jKCB52WMGZ6%LV*lEp5n*p%oK_Jm^UXkt>Yf zM+A;lu7!xz;Qb@=Qc*R?b!lDfqIVgq$0tk2eq%1jL&OrZR_;5sjH~wham|nSNJf9+ z-|0#B=?|oFH@NqlF2?!1my&p2_fi)|i@aOmNfSbBBlj2L4;h!s19MdV_aQ@$$}b|< zIirW{W6CPx+J(w*K;&c+R&+4F>uP ze(DcDOGrzo*XhTw@yCw=Qf^>xVBrJXEx|gXc=o3{LYYfB;@7v z&^PlZR|b_8bx6O*H-@|qXIu4dUZ`l)b;+@+V2`LB#)$jZGtX=55nn>q*+qZfCRT}d z5$}U1tEZU{-g9D>vI(@`dfMq>W;ObPKCEHtvQ!N-#XU!jB%Kl=a)J1_Rp&daXZv_w zK^G%#X#HcF1kV%KH;8O8AtgQ?egz>D*}5*7&+rE9#bRS2;*t18q#M_}^s;9yHFPTuPf#pa(mWgrXaO;;9j*xss5tQQmS zPOQh(o7v?378y#+WAwIC!{~~T${p9yz1=Z7*)0U5@N3&w8%EH(+!E(AC#Jg4mk~j! zkI#>VI4iGUq>G%d+lpPvNPQTK7SbI1rn-N1VOFGjR1)LfaT#S)E9-y0MEdZQ=+J#w z-J~7$vCkwV4MQu|Wh=1jqW72=$}3p8V7#giv&7bah%F-;KdkR{+ShHZ5a`(Kr8lk;w))@Pqzu}-tm2zee@wGi)ViUk5HP!9N73qEBFqC zID0v|&40|g7^ynf)r>R}&;AtBSchrILGSoYg;Z9o9{I}~r^k<+>OhU@5k57lIth`> zPy21;k`C79jqJu#*+w39qDs}s?}vi@D8)9O`VsOS7}*}B*M$>4hgem{>`Nd6v&*Ag zoqYUqMLr-#Xh(m|?KRxJVn`ir)rcB5)X5svY-4TSsG*|FE7(6~rpllEIDR0cWVG=k zyaj#-&1=GGH6wb*Fuy(8b=G?l(CPq9zo=ZhPYq<6`KaYRdpkYpL#1sLH(2g$phJO7J z?fkJ5MHCC>D8-M;!25KFCvx6-bg?wdK#d_ci>=ETY2!^h#%$bmF~kN9d+x@#KPGsM z=_RpEoZff_xezn-na0$6!)nLzS9gmmSo@p&eb%wer*Y{lqDMLQ7rqH2DWg{~cCvx` zuw$1tk&l0lJ>=^#a_YcHmJs`KYwqigRj=cG-$Di{#1ckihA7(MQ*umJ1i$GSZD2l2 z|5_b&FvfY&j7G@SRm(TvEp1^GKYJu{aBl<7^*Fe zmlIEE9Pj>6Bbi^#jyl^jk|M~jXH@iIq<(%qJ!F41^V@l#S1m;T0)126W#7gs#6S*K zF3yy{icEFtn)@)vGE@5;Z}gdZXuL4u4|Ab2?UsN1o!u%%*``=0&!m~@Y2CQCToY%c zg=mlB&yGX8S0M;5s}BQP{dHMs#5+}J$Qm=jaFCuZB=(L)=th^az{OUA@<=p zowa{PRR<$>6Rx_p??Vn72&*;E=#WOgUM#g%>^h(I$q~lg+fqGPqbbCA68voa`|&(Y z))Hg*v)%Gx-^a8lr)H!?Jp2c;Ffuo-eh0fDrhjen-|^Hs?|5&OwG4FCC!U8q-G8V$ zO&?`rPyP%wE@T1IXM6@m?8DgY^j&}0Q9pmiI_R_4wvg4#ZXZ|M2huT{G<&ZTc&XP>{m=0zg*YlhzxR$&{$(cOc^VC=IF+L;~d)8*pWEL%HN0QXPm;FK|*9j z@a^cz7;JZB5C--@&DlJm&lJc+t~}>%#`b!~)D?W!^QMUkj&te0z)0|1HUIh<`B{IM z*~|}Le7u1U0s0u-kx=2 zTr*r5tTGVd2fffKbKqFVr8mI`LOk;xeYZ@EO3f!H0~y!) z{str=Qoe?9%k|aDdCm2;vXKd`ueX6YmK8BT+;8hUrDM)$eb)`E>DKou&q;q-DdmPa z@s%v3^AMSZrxvYrLpha@{1u)?ymHPLbFM4b#+jj&d!Cs2Uip-bY9uS)+%U4R@+&++ za+PS1!nKunZMcFG>3=%gfmF2dE2gkx404q%XP3^;Do+F(pR|^b^QTMpXkpca7?M4# zofP7%C$#F5e}?ZsPUyuxWLM@HuQ+k&wXL=j z_g1WSbI1DlWmzW+pT{g;tQuby+6-j*0_k{XMF&UYeQhuCy|7|h4dyHrsso1r&d?)9x;M#xJs06kA9$V&%#$m-_IYE~b*G;sxe*2DXtZ&_uu2Yvg)N{iJ`XpH?pI-i9e6SMIh^lKS; zybuu{eA4mR<~KS*fd|j&u;du$Tan_xJo*~X9@cEH2$^9YrKo?LVnj+F7$p#*1>jDc zJ#(+snj`8B30XZSjCy|vX^p>vmhi(`%D^)xUu#FR6(PH1$2`W`Epp$8#P`AXwoUgY zzPH^oPa9lY+=0*O78AqRMj(#y136&1U^X@oouChwev^~>#u)z4kdQtVp}$uC#VSVi#g=TZg;e!LMm72?+7bnz9bN?LEj}5ck#WI zmMZ2ochPo3ez41E+!eE9m;%O?ce$BoDeRisLCu~B{BTue%v;=OEqARxupVhwOB3S< zyAHWVyn>lJR;cWHbn5YWT9W68->1ouzzo!u^T8AScH4j9!6R~w=az5#vh$N#$e`oe zbkQ2tb?TyZRwvqKY2mFY#0PfGZtLPzyf)c4R2kh~>O;->E$+Y2ci!&Wz5LsH{;*GC z$C?vbWj~?B#7NtivDqFmAzXED-$53zd#nxLPM@WtzrDqk8~PY~Br87<&lNqRJ*KQO zp87FE@CkpB=d5NLVOZ`d-jRyr^D`~6exeX(E#4hd>iMRfdI?y&zo&K_GU7d-@if)F zVo(Ww)0IoZ%7h*6F4j`-HPS=n7a_j+Zd|K9e%E5&7h`rp+QN*qo+Urb;BL9J4gIXW zJ#|!M*?-I%SFle_7_ZvrlaGwQ?WD%hNl|ie~7eHFl!~GZocQ_eQ?FVelt&uSM9e7s}t?Fi(|dN5HSSYRc{;5QQJ?99P=_l z>|uX*)cI23eoZ-)mFK9PP)o*W(fQ;dV;7^4Hn3LeM1LVFb)8@@7+XL3|H6#GN%|l9 zW>4|{n|T5V#b(n<1~(9=8V)PpsrC#RZl z#|ts8Es_OE7w`I$$Ja4mA|xu;5t;eo4Jd!a&X3==+d%LA=)Snv7kcE&H0)hICDo0Q zg;T^&u-8{eW7~To_kop*r+DQX@Wik`tmZ!D*M!1Tj!Yk}t%^LMzi{MZY|PVYe?(_9 zBG(pjm(xDo8o1w@UO^(8C`88u*CrQ`6;O{{z#kS#-uA?(M+nq*R8|z?4RfKVi3ES} zJ7^)}!ap<8MPE}$dB_}&iFL9~jNVJ^xIU3yK(-;IY^+^5=5?V?$uSW{yfKdLTri(? z?9_(UTE~O#;8*ZakmsM7$U8Gxwa!et6`>C zNd2(FIFO8a&2wJw|&S*ZwenBpC^gH{Xlx#{C3hPtfwj@x;zni zUqTP;=1;Vf&d<%M7@pIc^+UMdZuXCP{LS44dLK96GLhlmlJdkodP{%tfi+Azrs=~9 zuN%Y8k#*|tXPyTpL_3}ldCpjod&@2xyP9uIokUK4YyL3_JA#Q)EW9;^hQ1-0c%sGKvwpnly<&JEB3niT*S3#W^jPk` zDPbkd-7j(F{5@Gt6Yj(qSIt&Tmt4gfvn%&~VQuR@yEcFHHLv_eo=qgg9!3eST)mF* zs(WK>D!x%2tc!kD6yWj19wAyazPB6Q%eXd~iic6ddyg&DrM~(@AG6N;{P2^$&l8{R zm%hUH!pp`{kNYgfb|d0qT=;f`Y`$sV%oqLo8+uPzv+zFs8SFomUsNteN?uHFM=qu> z;1#Xqi|Ky@$O2++x}yj7LJR+km4&p8)ml*~%aQt* z$5&Aw>E#{5hKT4iV#Ha94g;_4MWTt2XdT0-`;&i%yfN1%=HD9j)4nnSLqt0f>;0en z;E8=XuN-+`_~puN2lacO?y`lM)mKwp$nak+az4oVztFG3oA$}_UaY5h)qG)u_SG(a zjqk0B3*xQ$s_rc0Vz1uzVHNyqsV($eU#mHxNBLq5E!Nq+CeH|SV4-Hm8tt#e&c;=X zpDcgOqsupJ+<*Ek#8vROS(6pRcVNvHW*(mDUig9Zj}Xz8D_GZ%2HXyJDU^ z?_clG1hhz6Ra647oUfA1)h@7_vaHVZ;#Gm-Rg~#%6 z)w?E+hQDnw)E1Hc^S9{(A@hE6r8ZV?zVq3KS;comtN3O*B54?Dd?Lz=_nRCe?#_RG zH-zr+@7&QI*6%zyfzCTbPGHBZ)w`!Y>|T4~{?7Y4w6!qi@N^t6MnRuGcgKq9r_Y8p z=sKjK+z0PKh!_f4OChaY>VOq8I0~8~iSa0-7Dy`;Z5478YM&)7T234OowO>>0}G^8 z@}|<4Yc}dc3&f1_OhPAl7K3oz=Q+ixGf0TeDL-kIIKJv|kq;F79v5;0aTXHeg&1=)uyxCs&&#;Cm#rTjyo_LD! z6q~%96eA$;ZcH&00HacR%%*=Whx>X&FaqDGI3FrB37OT%*G%7x+I?KXj8q%$Hz7`b ziID!GPL`07uum_Lde|z@!SnR~BYd{!Za9@7h4>7r{Rv6!Q0^mz>f)1is;Dxq z`t*bzn2=HXj}=V)Dcqyya-~urikOh7s^q`p zzpU6@`BKCzkdoQHJzb6D842;cDKZy`Z^}HR7*K^VRUt1os+CG2JgHEBeF_tsaK`|U zKySbQnLf&wq&PsE>feOyvMGXp@gJd>YIJbeBLxNlMO=uH82yRQlZ=)Bg@o%$-!$bW zi$wc6BgCEJi77%wPPJ@8%s`$gh*H>{K%+?19zjS#ZS#QP`pr>h&!-1rjeP6|neN)JLxUK9lt z(ydS~RLJ-Yxl5FLVx66TkkfFUgpijvpP%u>z41wBHk@xJMEe@=pERv*^o(@L$z{Rs zGtZatc24ujbH$&LGlkhPA>zZNSRIJ$D6VbQjG8b)S|0okmRL02&ME3PXo6%I|1VLM zwUA*4`Dj|AkQCJw@=9TAxxZSeW?P6DKB-g*Sp{d(2)Q3lnh1n{BhErzby6%y=|qZ> z5&s^=dxi8;`PLNOLS0ZHxnM_pAhq+_Zp|6FWx69?DI>-nOS-cyjN+ub>cVJ*kk%+C zo}!T1`7R0Jf2zb3(jo(d$gG&P60*`H1xF#*v07Nj^Lhx$U-?i|HbN1Wk@Mx4rj2)r z5LsASb^g=g>(2h`JE4WRUoJvu77WzAx{(w%7gS>gvifATb1?iIDaAKthLc} zF(JjVmo$Z_ZXq4bKctcVfsn{z)7OqE9L_5!U&$EB%0*8^`FbH@y?7RK8Tp`RBx+lo z;_)fh*-pm4Q%_WtAR=2uT|Ob&D*u#Dmi8y>_Xb(d9l1+?Zo3`iBlxq|6%A)F-Kw877dx%FJQs7P@9%UF0$<_)^yN#)IsgmB4kd;5=V%cs4 zQ5Z4oH%r;SP>exj+d;o1yEqbc0;Jq!LHKNU4=)3e^`RoAkZp@kW2ioo(oo9cmX48y zJhE&r?)XuEL~!xT4XX>i&^$t88dp7Bz7ei^%vz}?SjbBHM2uvdR4F9X+<4VHY&jx2 z3+Vskalb;9=0aYpjLfg$JiTyA+^CaA$hOM1O31-1kIbumR2dhDqyC$a>kac5dFn31 z7)oANaJsS(w~5>`>uSasE`i8x#OGO)XM_k@k-0*D^K9VNdzwhJB$zB20l_`y8(rSI*~VoCGiA zSz}IdvtAv3pF8T|vwf#^J|*I?7xOh4h$p zhsf~F`&uV^V%0>kn9Pl9GZ96MJr(iZjPb-`p|y%%JL0{V%PC&!#@I#)FBoI%vLG>k zb&-*FsqZjkzT3p?a`BOzEk)e)G<7BB)yY4_cMy|NF%DaNhb%NA=S#fbbTJORHHG*O zdoAQ)%|hv)EFZqNu7isAT3oAP0ZhwvD zfyHE`vZ<<4NG%g%TxGapZ@G}^jb_q+2-(`BxDVv;qjsPW%baJ@Ax)clIE3^Vc!oqxco{ps0o=q}73uxe|f_q*Wuk&7*DVq>0G~tcw$fJN4WNx%tC*m5?Zq zCq%S!VuquNd1GIjkdq$z7((jAxJ#`O+$Q^0V`?nk3$;d9Cce?y5jLg+F?ZbMPx{hcFo`s!%oO9xJ=X}uLuDhkf zJY+8sJ6xOC?d2yuDKxYYkqfx+%LOCG7)p&aEwB27{xz;mG`|1%ZQY%Ex`gOxVfR2i zVs==aUeAeK1|vVBdG)+-v=}k28>}Ii#`_>HbgWJnBJPh*db^w!T-zOv9iu|t*O)=R zk2OApB%XBFkqx;^APbqDkitCz?VE{X8$yh2NQ>#8SV!Fc zrbp3EY@y$desKHrx$y+I=rqD8TT4-}MPH$b`Er*+%W_SB@Wu#aV0~hXA{ne4X}Oky z-D`nt{-#l$fpwiN>dcW5$G;cM;0iIWnQPfaZs{zv9Ftyghvb+(%Bwy;k%zP#>c({H zTYi?2SGB0=$GU)4TFtzx^a3j8SX$}Xus)=f`3>;BtJUHLO3cI?gGsK#yv?<6Ctn z?Tjc}XN^xGBa$D8Or339TU*KbEVNTm8BWMc>>W1yzJV1)f3Yv#iI(IW@o$sg%3s?e zDumW0=7jNnGcOH!d7zM!fs}>ZMM&k0bQ?JtR(Q2h{a~GU8#^EL^n{d)`NlR*8R)sT zannYB@1u#JTxKZK5-Pq2k~_vNqi}Lz)lnc1vx-fw`(qZMPhBxmBI1%chA&g~9jpp# zOJ9L4td1%7W3;Hl$9TLD-&lFob_llN3bt$&`Z8@d=NiDa%{$hVv^}EvgptD43_{wE zof!Hdn8|N@){9UDcZ(#OPNPFmKwR_vc$U?i%2H*Af z`46uAzBI8iL5LU^KI!}H@Q#;HJ1|4oUd;Z)`}Fm+P`#tQ5fh&fnLj!oo4io2*3y}O z561+NDl0~BM~HJi%f6m3vQCqCh_i13X$LDuOx__^{#rDOV;;l4D+Wn-iGA-!7S2K^ z1iMHqdibyg&{B?uFGl2l(0;R2Sz$B9aG8u=wWS^0iV>Uq-sCys)RjOQ>ih_KjcXc3 zAA>v~vSq69?dU~w?Y_r@!K_-m2IT~*|;@UAK&O{{UY5*!KFR(3V)>9X>y zqpgZY0Iu+|+M)m&5z7T_Zxm90nEAHQgh4;p>TKL0X7a!e@=+m~Z!>F=cEuI6kPYIW zDP>_6TS)!Tt2S+TR2dBs8Ox-GC1i(A6eDF}j?&@^#2BQBAn_e+)+$b25#rH*NQubG zoW(ZJ-{HUGx#0w_Kz#oA8F@{3FId#4pele^Rd%fCFrS&Lh0R|}7$pdVYksVMcCr58 z4?%y)#eb7U;ttaI@x>rK2@j&v26)JX;bDYmYm4+>!F7%FZ7dt(FvG-voCYIv! zoS6r5SRuAB_o0K}KW?+5vyFbE5Sc`@6!LNwR%h6?m_~(&*M{*OA%@dBg^UP$BXX4*|JVdmyF^g?a{v38g2+;*i zXTiTHx}i7fpX?6W5dX}72l@;CY3m@f^Ka?8=M`*ojPCn)Yr~w8M^z2_G9LQI7(a0N zB^AGqrwIPgHRyuR;T0S|m^&1yDGO^`J(pThr$ofRUhMmj1<8)q z((};$L%YiJk{8G;Sb3peBDb}2#enBqI{Hka-G-{_QS09}+Qb-tep<2fF^tYYPVTMx zMN8qWm4W?C-bUeiWpDH2Oo~S(8EVgX1P3us>$*sAPFHgK^2Hpo+xsp}SqMa)1nvV* zK2edyW9kckA2H&F5gGSrQy`n@K61h8O?TNhj9>_niN!Og?ZMu=M>Peqr$}ZUtiRR; zEDDQj>tE&$7w@@$cFYjEsBJ=D)5W539P1_)0R>Whf6n3k4$VOmn!$)_n7S^U}343g`sXSr-X>6(= zr({~0n3K@qsT2FAeBSh7wTX~i`JAq=9E?V|KF`6bJzu?lY_@!BmWk@DF4x1b!^PLt z9>pi!UqStAA;}B+r*h`pFpuF;mxi$ok?WSQZqQHvD#qjee(ms$eoLtTBhu2r%3mQd zYY!3sQEl(HXVzTwyL@77!|(BenS8%5h!rEOH6DnwQi{JX<20^7%453|3qZfbpXbDP z*7G2Jh|Fq#$Ts@dbl1~6=%49i(bty8Rx(yH_iv_-5t%MaHUH6dCRq}kFc7^G2|~b< z@%~5ZP_KX4UO8S@Wsx8vG-6bw2k*o;8tm&K?bi|&G}-@YOgDA%qbBg7kR)>lMKdrA40YG32-0y{hUvhzi%P;?-EQOdK!C+CU5xEE!wj2-?VR`uST5aM=oe@yJXDt z>+O6vS5e5*yTdgIqlg(0Jvv`v6rqQyz&e&_0ba3>Oz+Y%sA>C1qA&Oa!=)E+-J)v{ zGL$~HeQZN!$^&~|^%=$8_&p&gF&f)N_$L2<(kTsyUfn(;)dJt#+(8u)dC>-mkX1_J zJ8MO61bjbB8iP;CYF|bs)Ws7Q`mcpZAL3K5ZH5ZDuk$U;v=9=hT!_4HqsHjFq>U_E zf<+Xc|HgG3@%IR+$U6I$+~qyGTXW0;={siq@e|y{eNKf`j(zkkUC-}ST=}8a?59?L z?#(SkVhY#oG_P#=PXD+QM?brE>=M$?shmaC&#eXX()vx7*;?LkL*kpeRwi4QtD6pU z%=#U9V6|7jJ7V;6HyyS7I{ zA8~)LJoLwpRCF!S6Z{9^ux4l^OFZR&XM9!<)>e#Y=#Adv5g1k=4RPgz8OGy_Uhs%y zF(lma(S)L)mwIHYT;(%z4Hpk0*SYVC^okkwzZOzCYhp;UZS+P6DIeCk3h{)Q79(=% zF^Vvvr3A+7heh)jIwiw$X_yl{r1J_g$UqwEg<-vLW!j~exV+qj~2 zl+Amz&dUApgNES5Um^8}S8BMYj!Ggb@;55i;X6Bei-yZAW3Kz~TNd*8;V%zVAETwr z^H@Zy>f|TTemBf!7)>jR)i!G&am`8goQd zJR_03h{zSQBNi~`bDXmMjTNQ;_~zDW8+`#|-8xXYj!4PED8ksX%9x3NIrc8xX;p}E zjpq;_E!aPPoRl+imvO?ijQ932Q3pI(+Bg-f3xzn&dyfGF#@&sz-AdziJGwDYX9coR z%ZzipV|B;4UwJsMZQK~Zitp!ocybU4-xGgYH$&g;xZ0zbg*fgC&5g(oj}apJr8o;q zhcRd7NUzj``J>~tR58APFy3n1*KoWjM5f;}Q<1S^M3)vS39%hX3ECk-oM#}-H0#C{ zok@2v-WW4oe^|dTzf!py(AR#58eLtjCzP z68Fgp#4zGH?TIbpKB!;i=!+1)=;N9-|KQ|-JiN1JjgI2{X*b=D@6=Ni(j#IL$hbpX zN<85^jd|kO6<~@h7mTP0`H>qD$igbd={;T4Swg;r>}iUVSFF^SE^CQ(EYol1sjXAY z+b}*iTjXuv-?T4(=4m5hzA&?AMjZ{_jkAdeq5nWg-pEj9>zVuR&o=EtzsqbZ;WT0) z$0whX_N39#9`q;}#ntIEvZ4^#jc0C63HuMLqlDyzIe>E%onZVuqUsA{EK>x#V;=q- zva!5FbZN2gY06+<%=r;gI{L+?%-BXHh=ar4cXy3dmGgF;nw;MgqLJD_EnbGV5E7j+V=pml%ccP&;A}<|$ z3oBAEkRz=mhJtmJLP|q+yOKv2Iz)7Sv1`LhJAb&MOMADY?OhVV7g^2v()S_bTG59b zZPofbKh!cSs>d*gRtBj#W?8Rq$->ep5n_{9^gH!`AirMU)v}STTHmW1W3KxC%)LNX zj^XY2ests1BBrbAwH0$t@09g&{~!v^$C&B4B73nk_T$QWnJ=}|K`N#SVV3N*!+Q1 zTJegkGb&OJztdpRYR@5BObhZHNNLokBK9= zI^F$U{+^z=-{9&Zu^1%{gjMM3(+_f_)laT}Y?b>lj&Q8;-xvpxmM*G-HF`0uyZ$RR95G_d8gNNvpQUakeBR_p1F@(uWZEd98d-c6jVE2QxBQ6cRjOWP?+aMLf1LA)6i z*iO~hhU~OlCkPSALySA`m^F?OwH=c@(J!_?*#qRGLSz(a-S#(A<=+$f1N{_RJpMy( zUNlbPjs-jCw$LB4bJ1l+J8|dk74!Xf9{#X`V&`MrcYo(w8hTE5Y2~5a**c|vJUwKW z=t_fcyWE&LMTkkDV&=%U81u$R&W?<0TWK9DHMZ$@Vl+wW>+5)92&oNg;kKchMCMl zoaYB`e|2G;X-mmWw7t@KU&YLS#oc=5IJyvT=#klN<%TuVyS+BCW9aTBFH~TS8kM*ZU+68`BUeX%{2r!#z<*P9QgKB`w}Vl|J%(M(gWN;+IY#^T z|1?$nJww(X<4k+Jc6r@q_J>uOqC1JJS@-Ne*x;T+_L4tsIP>d0b%IcTXYG}K%=X$V zhF{|v=u+TH4Usgf=ttT*JQF>^QlL@x$4Hy#yWP9g53_~$uBl;cckk&NV;@_8v`{kz zaxr?jcccsBr=2V|h|hLPnaI5Nnc2|SxzCE0c8oaZAMCR;M@9B|HDe`!ke1oT3pwbg z*f*-m@YA9q5wpfc*8RhOZnGlm`Ju;b-hU2Q%aLy_Nep?3bj`@twX>2G&XJr+XW{I6}N)PVjybwHW)@ zZ@0|W^4-jHWA}S4VU55^DREyz-ObUz9T69cre_o48>@Uy>gL#g3F?1FJ>HEcN&jGH z(vzamDZROqq$My<^`x6GX3w0M(T?7T^OamM7AnM8Go8=ML-l@wg&^b7YL<@q@h7Us z(C>1jOA6jU=ex=H{d_NStO&`;E8gfQe~If3g_vXR%E?9hita2Sw$Z;M2H+k2VkdXw z47`(9{1qW3Wv+&QI3*@OW`KGkE<=ZCDNka}>Ej*!;TKYDC^9l47>sZHMT(F}`sRh3P|Sk9?EUCeVh zb(Y6ZU1dY~oK~?;oTzl_6-&eKalHl1%s$aF6z}@uUIu!9rKEA9s{|ufQI{N_`Jul` z{JwJJ?nHM!%q2Jx$LF2cqHQ*=TPxAi4w29@xO-d62P5C7jqH45v?+0jE81q|*wktJ zOX%A;?bM7h@zYbzSUqrhj{VC>YZytQFzEmG}K09MI?cd zs8>N`NA?vWw)|MdHz>E^I0G@s675 zNSJigfJdZ)HTpT;xKiVUslY7wqaS3L?|;s-g3)pzGCO#-@0TZ^Qzs3g_e4tmg! zuGxms<#Ts&^x2*JTY@vYVMa}YU&S!r@zTsmxYSuekP4h``~^xZ{#~Sda6So;NIx3g7JkL z884Xec|!rAr{sQO*I3VVbJq1tJ4A?ay=J(7Y7G5vLVm45j^2HA>TiBEWA{QKF(i?Y zqL0Jss4K}-(eEM-&bdZo=Kzf)|BVGh&nE*}32;d=Dj zrSyJ$Pl!OYV!hkM?kc+HqmMBmSAL%Z$hHU*V&m5Duks`@&ZuL~K zi~n7b>BrwQqvb9kyX1$N7DC!Xy>NHTLC2@wI*q8r@Al|=g@`V6z8kmL4&U2<`!vpE zzDFF1&%%ijco~~x@*e2vV zn92N-wl8|W-Y4;a_U(OL*Cwrh==(+t9YJK3i^r;Fd6+2(P7&3?F=ZRjkhw0@ZvP?Vi}A)&;!ydrBnh zl684SHiTC+va7JC(m#Ug@I|ZueH+idDCR+=w1F%@_r;GcJJ0ryDOs<7);y5aygD-X zKX~<2&f|LZ8h7b^_2r4#GtZMxwDHfs+Ifd~6u#rLx#f+F>qTBRpDb_fqIcza*BQp& z)1-qjX7rk+gYo*O8|%X%$L!`u$(uF%=V$+cxUPH&V=)M&y%pMf*02FRU+l!);>z=o261 zF3N9oofF(U(M$*P4}_G39$F#oq0ixsKrR z+d6ujpGYi6poA2Et~`07PX~RS@BPZ?5c2VCA|WkfCX7^3`*Axz&ksFJPnYAvnBJ3F zzL?b_qqP zv5#&UAyz)>vn`}a1;V}#BzNj^AY>SNwuQ{_1tSs_8L)S9v^jN(KncnDBOjNL<~VOx zNKA{yr?xo2_&3^9PRdn?^eOodge=23#X_E*4Og_L;-o%=d{HRQh%HhQLbBsz`{<^c zIz^L0H9Go#tAvcS`2C29;T&8cllvd!6yufoG`gOF`Z@d~I9W@GF3lfEt3Td?7`ah| zUC2y{a<+wRH>y7sa&l&iq<8IXDPk-eu|Xl$8f9|}acjJH+NjQFp+#&Le@|O^5v$43 z7_||r5mM?S)h48SBNZw{6rXY@h0OA(E>y^l{}8!yojURoDA%0ot6{zH5Af013U&)CF7?)BRV!S zQVOr=`7xs^kXA@*3OTLt9=&EiPS$O@Xz-W5^j+#6B}DfeMR0}aHY4sYWYx*m|7|no z6mM6j3_KxM7c~B!7vo3id!#2DRvZZVVpSbP? z$dIROEg?^mAK!vl(jl*hkeCFIt5ZJ5sb7IKoW><2JdOBl#T_R<3uzzrvkRpB`n3Lk zVVB-Oa-qnbkjWjzg`76YTl9}?WKUnY=^*R;$}2dDH-EwXDFzmk1|tRU2U2;znPe8_ z771x>UfhYcBEJcdD@u;P$F!@cb48Xst(A9tEu5PdNIlduL5N8r;|Z>{RjS+)GNhhy z$JglCiEpmnZ?S!=FS=q9A|-(t1A$n7rn*oeA~uvYAw)cc-;clC$TRnkunvxB30Bqx z;+SP1q}V3kpb%|Rwn)wDM_#rZV`#B|ypSexhZZ)*sgfTl$5_xw5zib$ISX|`%KyV9 z$;Xw${gC(F{Zz7%bV zenv5@KNaGIm0k?4nA9&#NKcrJnxpAFc1{Y!6J?tSdEesmGoG|2+QivPMjrpoUa;?q zklAp?fsiF3!N@L$*KNrrQSNadzNw!>j&^^C{Qg2px%_)p$Zh@`w?TQtLVCuYS2-Fn zvDZ*Y!#;pQjxF@r#Wnl*Y;UW7oj*-PwUBpTh}TY4OG5gG-F$?MZ&H`UK%DhY6!H($ zSy3E&*&dvaBOF4q8^vjbR4csSPA^zB8%P_>qR*(4X5I@~F&Uc>nUGD{n?iJFqWX}K z;rgub^;$-a5y?0O{Vrd`G5Bo1@R{NFyk?u?!GZW=ZW%#JX3=jqUW0IdY_m4YDFTFy zSSTGtB7c-@VmL@U#lVE@w(!(Tp!z|^B^jDnwFMo-4h(_#N4cOmF78ZI<^plvy>W3@ z^a;hai8C8=7`RT^GdWDfz!*yoEkk*#IbI$6UE-%c$`8o_C5*AeKSo)+@&EHgPe~Tv zYSc3>km3~m7xKhm>HOq>k{fnn7qX&elM(AwFB1=)D&>X;BAta#ZB_3FPl<`dQr1K) znp6%yL5w@ZU1cLWJ|S{qv@ju6_Y!5S36Yqg%y}UrUw*cFB5#|K?C2%PQU!xgy*H73 z+&ez}qIWX8ZY;btg*5CWEo6D6cyNwkVA%OG=l11G6%g1H_- zM#WijItscqQ6cB9Ct%y#yzMKE&ipW+eK!V#|Y;(BaOY`dCZLc`GmZ-&>xZ|7B>1R@{|!_+dHjH zRA55dR|RD5v}D~ zb+GN-5tGJs6M|DC0x3qlO#*R8mX>$3g8uk_b4`JMZ^l z_kpYpTAcU6DH*GPg}7cGKHC{0-}nSaN*pz9J}2fHB65p=pKqwNnA<9(N>I}I5mFPLpYlaRWO@tbiwAyFNR zZ(Evrp&zzbs|D}iVzX7W6ft|}&@6@MXXJ+Lw$y9|^ES%Amm2q*zMegrrF!JFBhr0P zm9~)43Nrjsw;hbw=#)!NL#?CZWjGs5$R>o489yS7X$f@5N%RMW(ZzHR=em0 zEnOUmdOr)9?V&<0L(jn9GsHR3BU8j6D_i9u%|zL?LTnNCRaPMt-Bbi+yjA){fBLLo~A6ZhLZhIMTc;@LJ^e2ImV{EEGivEP=yhEZ56WfEUv(0O8 z&9M8LkRAz~h(zfyzJ;rmMlDnBvx+g-@=AJaub=rvyh<#CqjBi1&AXBd}AuHN)B63nf zFM&lzNYt9T6l@^pv+$WwwPg5#(?GxSe{K_c4K2r1P+Ws74T_52@@ZHtDdc%aMjQmT z{XlwNm<4Y#<=Bx@$kFzp(`YhqI2%HL$a9>U7n6Xk(Bw@aqUCtgn*L%SKQLL*h0%Yr z^KmwV*>#)>nytn_J=_O%T<FCGBRM=AEr4$G+jwI?6LIp~qGE{cR6 zP3tRN^psea*2Ii8A=-=henzeb6{QWSP0U9!QJRENMUxC(7<0AJwcNM{LMHmsY)o-- z{+`Vp=!6`ddVCA>)18jLbjIa>h&yC@dsLY!WTcI{QApkBtFd{LvE5lEImW74K{ zUA6u=jWVx;Z|60lHx42(4-(g4MJ6-8xpgFx`Pp8Q6emPSr0@yey$tH+BO97x(hwsV z=UNH*@?oAR#bOw-v6ICeaPOS*C1!R)26Yt_VrlZ(K0O)V+o!k3Z~L5o$Z99#=DOLE zUojz!73exmgKgDy9~iH@Ze82BqETs!nWc7Lsm%FqbacSK zd0*vv8PmZYylyWh`r)aUZ2T|_L`WUf6Gn)a#P;C1b56azRxf&W?Tvyn)pJeGlUf4t zi)Xv0R!^g|UDGOj2iGWnjYWTN{dyfVVNJ-eiEOZbyK%)q{f^i&f7;Q$#=rT`g2&Z~ zTyyF-7}@a!E3N}+n|fIb5$!~OZ_UFhYO@+~E{x>Yyv@)bQnQp1`UZv6ie8+W@5d^q zmev;LA5>|Kj{cn*?jp1Y)v|ZYAFUD$mHOEU@!T-#TTQwMz0_5Imt*CfXkr2Ad#c*l zj*Lsjz#4i9w4AxqY?WO7Gp>QEI@%p`BdUAud>XyURN(LNE{y+XM;C!lGvd(WFCDtKF^V9hT&!%UQ_I74bdKDxioVV%7LDsRSEQ^Vvble}ke4@VP05Jp zPKSu6hiqL)bP_{EHzWEf>Kfd+Pe{zvU}P1Xl__M$d=*50q<=8Gwyu{KdKQE<=HxZl zIx6J4gRJu36dUMssQ+Cb@u~N$4|Pf1D|NPs`+XhrAnPgm4|n3RXvWKkei9?yC+48n zVC&4DVgLhrLF;+Vk;m7Yyko^dyI)1pQ{NX%QW zQ5%c?xB6;-;i_{XR`_H^z6<8e)o9{{TC=`Y*|SjJw#{5Q+AU!|xe(jvyLH)#f|2|f zR?{(JhAN4QNtQjd{>-Yp;jT%@)S~v<2 z5yQu<36DN5_!d0p(onT|c!|*`Rf?ex`%&%j-(aZV@W*G>v%5R}WMihRJ{TFy3q7~&|YWw4QXSdTh zchV&(6W$mu6T)`KB_-IxdaX61HVb57PKn1bA*{vr=%b09lROLpdZj#Om|=uah&$$o zL`1xQqH1$5En}{fhsMCW#J#nS{vG#OI744ZGvtZxt4)lKx!-8|%gBdQ>I2D*Ph&!^y&M)4?N729I?R*72fI>>g8!(WIkzyg{ zsB*kT1|Ghjb`7Ue=kROejKoOz2?39Yu5I3by}d__4w2yl*^~F^l8fInzK^pRd_-4h z{IsE8gIrmN4iG-|nB3V{NDQ39eK2;ds6IpzwMp@u&YeE2Xg1$~6(N2Mo6}|28 z3G~pJ*KL^%)U+P)1-6MRradvzvhfLC-EnfDkRe`!Z+k_zW`4H$Ro)?MrN?Js-SW_X z^CLQJIK3*1AT{=EYqytz*{HrPb4=W0usp^aeS7JcK@`YAkBsmBEw=vBOdt9jd_M^) zycbTYk9z0}iB>ZB7Gn4g<{Acq{YL|78GQjRBUUiSOnce}-`l5uKX%pG+B$g4ob5Qf zL&)Ra>Dh13l{w#)4WS;!LS{Mq1b2Ub+=0$xTmX9JJi2mXjjo?X$TuD@V z+qyIBq#ocxzUcFYNF<=q?`cW*EZ$iyNdf(M0x?MREVR@=L!Q_ohZj#{OEZR%AhII` zfsijc?&7n3U%MEWX`kpa<_(5>bl_dzKGQa!H`hc47oL(96KZiMw(sdO!`~Bs9bVA- z>koWm9!fM8Nf>8p5gS6~+JY@(C(o8!J;*Iv-jU!-NJ)l0a)o?#P27p4Yxm+)iwAY| z60}I7#WUB+iB?RE_{aOL-1IOe(PHK>s_G^+XBeYwk?oIquIUddKbzH*kp&3RHH_;f zfo#~(RVTzg?3^T|;>=+o;#;VHgQca4^I5PZ#>Ky9wS|>i&92N9u1>Ym`KjZFVqT|y zjjmMuJ#|Kj2_t^77geh<2N&(AkdB!nEv9jy`e@Nl2z79aL2jtGh18Bd)fSyIu#=jO zq6j{JS{J#)rxx`dm{-$8g*YA@(yUb(tdG`@f4mnM1^B+;V@Dk5ea?Q21m`Cgc^^1R>X zh)hvLX1?g}6;c-J(>7y&f<5lhDJ6~!Qrlf-5#NF|)5cnnwwCU6MsE0v2-&GzK0iBI z#|oFWqh$@+s$C>A_V==d|ZkH?CXHpA)<=3 zM|*S`MO`AKEvz!_WxMfyE2mWK6xmBO{=&TzMQW%qdQ3&YZ0>GJ+!A-MrLr(LyjzUE z!SAuy*ZAfv%Kve#B{jMFv)f7!Z?i7V#yzt|t0d|0E!1j%&ZkkTfGah+R6)f!V6V#y z@7}u8uV>pCr;BO7e`TO07vdGYliiDU0{))phL5+ikaDqOn|^1^)j*_u7u8gcELEDX zDMXAInVrsPG@>A+`S)R0jV@DOJARs|GOL0JJS@D;dbgVKM(@2E=X!J*B(4VU`nbY2 zq|Y+>_w>VmlU{QlQG9~dWmu`$O}mfU96e@zv~*BS^)W-vAQH8)xYpw(&J*f0TI1l} z5%ZojAy-I^z%L@Y<}iZW=Smm3i;!<%mRp~{`os45d}B6hUtc}Qo+QBXhh5(Kk_ts% zKwoZ&&%%<>!d%_HPCC49>)6A5<-Sw2NOY>dz8jE`j*DP7=ITh$5~}Uu6MSU z`!<}=9vZmt6d57yVNPQ|F1n8Ec80VJzMmsyguTy%*hF>M&-%e(y9g}TK=t!J_RRGg zg~<3z<3dNzcE39HSZ^kzId){}w^p9>(64{&s}b^}Z)?|!1Pk z3|<|7@hZ$G?{~KIV|7ls=$Y&H)r;9@{grQ_7bn8b8RHoJMJ7J}(l^RLZPwr93DZL2 z)zMoR>8lG_^ho);n9nzo#q4E9qNg|_t`ki6!bmMmoCpKVn9sc+MEplkL~u`GGW?c`syT&MY0}w1P@v*!RO9639he z64f#T^ZA50`%s4Yk9v$@GbVB&B3p@mB-ya^Fp52Dggo(=Rz-&iPrCX(Cb!&(si7KaAZ*0ba#Sv5siHd=9>t>Vd2Cea$eY zI=_=1*7pjLMay1~2!?di#~i zU9(f0{n);K&Dl4bZ}>xBWwIAAr&W-2)tXwSP>0MExi5xd85Bo5NvVsK`r_8Mq)L;02JJEZBzRW7o zPfNSK%8Lnbgjh7C;9CoE?BH9n@Hr>3tUJy+52SN{{W2*>>{S#}Kh)04c3OBBR#Z{p z9M3=w^874a(!pzOFG{cpsXD%Re^#A!qm0+rln*;kR&)&g_?w$HMnPA7y<@#tloD*L zh+55L#^Wt%Gdc(m*}0&9OkB|}iN5n{FMr6~CnV}*2=O8h44>vDb3SY7fIhT)m)FXj z0Lz;%v{2SxfjsW1Z2y8@_3Awztc+WIri|Ix)rFyjvby8}dX%eQEj8|Gf4yL?ZH*Ww zhkrAwU-W~8^pAI83^@|G)_-#m_-}UQD79oktr){!L*4?$I)&JOzz&x+ZsBa~8qb*P zXifRySRpCjCo^lqnzLW58(cC(9>(96R0d}RghUG+BJ%SvSG^?S3rnP33P%anHpv&f zKRb9I8EJ(($c4y;#dX`CCCn5H=>@wb*C#(1{VXA7{5_w=up>ljWp<+VwI~Ndq^E~0 zeCwM(>iq*bEZ@F=71Wc6?0nhYIXPdJB19o=nYei70fD+-1D;Kf=hix>F|*Y=xA6?5b)NIom33S1yxVuR zj-JH2yQGTe;7S^xKet|tpu_X_%r0_Kt>^~FDCl~!;(1Sh-%HMG{a)0;1F}N92$Cg|4Py-6Fj<xge1vyHJYM@SuPCw?(Y8&7ZW(6ST@ zkA?5{X~ta_LK=4*>=Uis{IpXCRuJipqnVSC2p?d?Y~Lt>z<0YnRg6XNt93)J@qJ}o zvh8xe_F)!#-)jwP4TV(WU3eD!K!~*bFpjjJ%X!A{dA)F@`F>Noji>6(`gjfAY9Ho* zKKA<}oQKFs18bc3yYfuvt#y53Oco*~U)(t%rC`MQOVOcB-}9yB?74n_HB9`p5ZJV* zlCNo^-u`mV1*c#H@*#7@mrG(s*Dod{V%AF9G%;JumxW;+*Ozb2Ki=Rv&wKw$sa*T@ z#f&x7r?T$b#*_F(u|3v$eC3vkS;fzPT4wh4&$?ffmY)q-^7sZeTEUoukbco3{%pnc zTts$lsOLUA&9Q#;vs(^!TkoLjQGS}gwK;5w8ew~^gs*0 zfB3|#{O7uk*hM0wU-Sw-k4Z!LZZSt1bGu*9jbk-mk7gBI(Z2J;F1fE?V;|!iwLA9y zf5Y>K{N&$QEn~IGH$LB(S=E#J#=10_JL#h3@Xb;_td;tv%SXrkneGieuy3xC_-@ag zJ8If*o-{FP_|2CSMz)2NiS09wbcn;Rq%i-BsUrB5^tgrfyHF%Y$$A3H7#+=c&W1=6P zs$EOsTe4$1IqvB`sqNdMVkGli9wFXwy2~f^eE4ol3A2Cte`%>R z@^wZ*l<`~ZSY3>De)=P3vHU(6%b0!nPD(tP?mM|HjCp@Yb~oFJXV}F^y$~N5o&0op z4rThS)Ub{lKIPE5bXE|L?`e_p)7Li)mf z=iuB*7~MbBv~oX?ey|qw)Ltsu*TFEIG1htN^U6wc#50lrMv8 z;L8g}!vopahkRWAJD*XHe=1&n>z5z)!U<_y=_kZH=3Is73ge0IG5iM4TaVE%cy_wA zf;<=}x*^a%719gVy9Dxnyn&~No{HLUoh<0~Kbtjczim&3nv>xb6u8Lw`%$&Wm+$2V(6)e)ZSe`sjJs@Fgs#wpI+ z#>`e{=^*l#uIY_s{En6#eA-?$VKu|quXN;#KA)w{XXmp%uowHtBAzUF)MsNA@*ybe!uoV|Mx6(HiD|&XpyCY-K|F$9Li0v@m{fZghX* z&b=J-o)C$#>Ue{n)n8Am{xH&bUe5!0FwUFhvUHIhZ&)#K-b&0Yf8Likj&$YJbGG6} zbO-H&n@V38bGXSe*P&<+?4WgfvjvYOBGoHK_it|2&{DnGf6GHJ^yb+TMr3Zjoo9R9 z(i-!~+|pY?7MB~<1V4~Z(vMEnE!Q(vz25R#=C`&Gy|~sUpB8h-fo!~uQ8Aa$Z@d;X z{`l5563;HYwrl-?oXk=8D}8aXYC=fk37@yte6jOHNOTq;;+5H!OH5KKX6h^8zZ%T-XN4Iv3q zm>FA$Rh;E>W1b#L|J#Se818BC6`*z&(i>()ZvSdye|gZaWFjIXcb*n-hfXhM*oD|a ztto|GIaYJWOkDkZ$1VjU&UdEXF^_(yR0*GuIh@&I?wm2|4w3Jd44I!lWSqLIWoAIV z(aeKcUm-uZ&+Z!Oq1AcUvW8K^yLNiWe=VdlGs0b;PqY*7y7th*zdLoq>e{>Wj}HI4 ziwp$(f1Pd3(L21mS>{`^n=?1V-BULet$@AK4_*Jsyk-ge$)Dse6~aA{_wygq)`hgh zy5y6EkF^B%iw=MOomDU2h41*rsfPFD{6m)Lynkn>VT zkN^IauAub4Ia;?6F*APaJEhO?-JXnFjFbq`f3e0lc)9WXZAq3G^ngeV+{JSsub^Wu zkK7U+MO>ZN%VI6_wpFZ3{>xdRe0$0ILDt8YV!{CbO?B?edC9Y3%g7RW{&EXOr*&5tZGYsgdbVlo_75WSd;fU$rVJWSYa{L0eh<}Zy( zS=^uSGSCNnR`TYjnVb)_HewR}!VJ{2*)HZnpRK+y`~Rd&Kg{Vpd(IOVBc=DSb}~39 zE?PpbnlaD9i>7moTfVxSs~KM1d18Nwe~>!({?soqQ?U?F$nN#z9v)U;XU=3vW;u)`cRfgo?7R-5HrXz9{<8UMc*v4 zOw5Y*+zZ##KOF*|ss8*_=BIc)x5H2Cr7_F3kj`v!k`iUHX6oJz`)OZ~q89wpe?Hs8 zp4Hb^<_hE2<-em>Af)jG$v2W;jG(`fBO<|wZH#RQsR_N%H)2!@zrjdcLG#KfK#R@|xFRrU)${()2y}J2QB@@g&ZnlYsJI?%I$a>CMv? z%h8~};j^=T)vml%`s3A+1J*E0fBM$!TpWX@~w!b;a%9WgZQ5AB<09R zk*$pKU`N^+-+Ftmm~(wwehKDs-kz;vWZ~^#bim!-JFOq@cG5yl7$KEoPw&flW1Z0Z zYOQERzfa19-%Y+5Pu~&J7u(9;$j*}do0&UP-zSTL@9C+&*|t3W$JC;CeGURM; zSkWHH!Ore?t~*Be-_8ExF5KdcrT^`|_-5VZ1MB(TeL+{oh%fY+UU14{{!~a~j`sIj z`p`EMVu>|IsFn{U0gfx97u^i7jqrOag> zkiTFt*97ON1yWI)37H+&f54)m1UYU3sgrV)LgYzM<-AGm`6KULh&(;&I1pk!sq;ig zEraR?Em+wWi7>NcTMmo-B6>|HasxV8Q^lEs2y!xUXSRlQS z9U`QqPIky&Jz=8x7=hSFHGo1|HPSzUIQCHr@r=_3g&bXckg$O`GTr?llXCBJ*-}YV zQ({rtx<~^GsrjR5e}s@G9sC241&QZ+Oj+OjJIgmQN?4k;53+ZJc*eMxkk1NvU_x{f z$x5@dvR~@Y5>j%bI=evnB#r)uES7Jp*1*s5Uzv~(ha+`h3JS-Jw+hy6vYwp$>+!Ye~J0q3K&Sy>$CLyOi%1sG*<>Om_vu&!C5>n*o;d#3)xD^rcI$0v!(J?>BMp11N@(nY>W)s)awvj$!5)dMVN_At85tpxCx0V2IB$Ctzeb0?(L!Eb$Rx1zEBz3SOOjyb;fW1rOB0h*=4SX_T-sysc2Tv5-|i^nXg!3{d_@ zUTcy$NP1fyZS>e9BBv7Ly~V11R5vK38%vjByB?JB#ZJ!{M=Ev;b}0yX8Cfj;%E_f-MiGe4?Nw4n7xuDw7ia=}}f$rg2PMc<`#2A+^`G_4{$g7ollPsteISw1dY>#i_H+$mM zk;hVg{LZeN=?dhb#~(yLe+PY;@3P2SBwpD_thaBofxc$BUwORNFQ-G?CV@zn<61v6tni9OYllzB+m)jPda?A- zezTOUQtz@wfykpOH;ZKTMZezCN*rOb^hG^4BHB~bjY4!wXmujRf8po@TFwnwLIUwd zTsjuvaIEhQE}xpIV^l~?xu)HY(9cGR6akS(b)A9OBv$o6DqHyBpq3wR{h0s2!catB zspuDSM<3l{1_a(Y9eKmb-#{wI?Cj5L!l%g+anz^!m$9l}Kv00=8(5369*Fd|(ccb> z9H+wySut`Rk(~?1f63*X){D_&3l3KF7fkAuqaJeFqBac?9Z}562af=9qekwf4dTua zBJGU2(dzAA!xgR18b*SxYgr z&BXB#A+kg8W_c8_;-1PYIV>qWa&5RjKJw1E);`+BXoR_Ze^9JTGikXGE4<7vGYAWL zX;W8bAekfELi`brrmkG}iN1{WFg$VHx>w{A60-WmSf}-ykV(yKjDoD{LiP`hZXL>x^W3#62TKs*z?k1Y%gpY(bAPi5)^nt;X`;)Yhp2Jm`!hD{vQNlXC>n z&dLWfgm!K!SjjA;WY)n!gG8OfQkqe|gHO9|ox0=#f2myi70BXuyQ3bObs^+soJkX{ zczF^ZaUlKUyYQmN2EX*lM#5O95aUX7dv1ez%L8c#qj~mrnivHY(hEjGYm#)sct}l6 zx0sO_nM$jpOUk_Nfz*NiTurwRtmvrn3NHVSEc{3Pv_d>@w8@1u!$?WZBQ><_0&!wm z6|!`!fBA28L@*;A$cNdNT4G=iKCQ?c#amKKk3JhlB+2OP)Uwk>EmO-&$dnRD9w=R^ zrEI)&GKB`lyo1N)pbuDUDhWMG8N_br|I}I<`?R&zelX%(tM`KPpw^L)rAo-^oI|cP z`~`mNW9;n;(Otm(sjoijeX-h7zZiF^O*a^$f0MOE6$n4==8fJ*mC9tDC8Q!}f{@j* zYe`6ST;mz_sGa0DIPG9|thziFK5zbhRG&rD#YPV-s{IrC=z-vLmzW%%I^8 zt+UIWBl}uNGt7O|IW1#kq0S}m8CPeXI?-pU(VoSpYDs5cWT7tCmh#`6lri%yq#ULmD8p93LWmnNhSjN;VWYUW+I zt!J#osdp8Q;n%xM?}Qxb>-89!}B zYXc&mVRw5VKG1V*NmmOa!BJ4}*!$A5*G@c|LL{#-FQebk>fo0aYaG#RMRpq6VnVv1 zr7A&HGuB45nAnDOC@uJJco!_Ce}xeysikV@mk4Peo~mZOExaYoCeQ5?@45!yE7nb6!MA&r?Yg?QzzvNmSqrx@`` zyx~HkmVp0e{m7Cw3DJtxCT+?`=QWtc7R!SmN?{o?yR;@s9r)oTGW|N$z1rx;@!7e7B#}CfdsF zc^!O0-pY?y1%;fl&VlA&xT}gjl(ldlzB}?_3~;HRztVH$1(;N{!5yLIm+! zYiS00)k3mi?8&3k7c(3l)0XhWdwFGJq|t*d4RuKjk>(h_%m3==iM#AHUd1Y!!__IP{gP8 z+z)z2u0w#+y;Z1t&WSS;kBP-apAP+*28R@jgbs zsOx>~%$in6f6T0Aq&wQ=K8|g)0)+U7-jvT^Z^Ct(sTa&}_;fGmqs!IJm+#MW|g=rd70+{re=CaSgs3l+}cs=$FBrJ3$7IYjAS^kkLq@ zw=;8+pRoD(X)&D@<=c^cfK_`QZF=an`3;ePcj2y%EaZR4Wb5hP{vqF!zo4^YL<@)Z zPEQioe|Q&qlJATe++ZxdxYp7>6{p7wnI-@D9qCi_+Pb#-Vsu4Fz8DSfK^Tf%K0R-_ z47A%lfAwHwAw~rZ^s0q;LI0;m7ucem>t)Hl#qTMr4%BmU7BXimy2*&ftYVirij$6n z$Uwm;n{3X)d}$F1zr3e*@1oyg2Mco*)Ei=}6>l6gv9wU{jpXE6Fht^Z~9u47)Nb=zu_-Kvaz;`ZK zP1hwz4_2RbzcpjjP)4V3>|f|{QTYveapW)-5@g!^H_|_j=S@F^Q-Xy=z!%r8ugpA+ zf4!eH(IyXM^Un4AgLAQuR4P`+^^r5q6bwYZ5&q67<-@MbF0pq|W5{DG?1c)ERf8)U z_BBQgyDZ{?)th}>`RJ%I#%-M?>`ZpB*1gZzR$PNwJF^`07|(=ts(r<{8hnFQa>4jc zAQL@GDJ(g{sI%6}g=N*+Hjvk_udf&~e-_BmOb2_*W!|TWQFkHv@okh#@R09TW>9wQ z%8b!`+>5&Hmk> zmSUt$?0M+lYuv3bq~kP{7!dChE9d+1T(C=_pB0h^ksWn&h#Y?`#q^1%%ZZa-PR zYz@9$H=j2lkL{4R-B~8SXYa?my&tSWBc;ssZzJ`n+(2aXj1^uZ$woupd88BbZW(by zT{6;*nbnNwqVl^v=|)=7kyHKff4q%cf2~fAm^1#4kcL`wBbuO4z70FJ&;t|V9Q7D6Ss0^Ie?wmIMB8%I zAoAy@S#_+z7_~*QEF!_4%WtqUPOAx|UW~2`9cr#Z6w;Y_dDPc8`lqAiAeuv@ zNO@>Mj%Ka$yU8hd7)z7)Qw_6lBg%m=J2s-=h3`)rzOgo9H2Hv7%Qo7z#B1H2ceDnF z=fruL;ZbakU+V2g{lW0ne=mAQ@iolHHT*Va-IBhj4PzT}F{ElqZwrwL$4J#$^V3FU zo{}M}>hLY;+nMWVba_lzQ!@H5&XF;ackHVkp=OX_W{hwd@KlYN4o0?xw9dC=Nc*tt zow*+D;~%rgbHzQKa*Qx$H)hm9;_029xZ7*Y8Fm07D^h2Oe3DhTe{NF}7V|%2USo~{ zEl!yjaURRs#F)@nJtf|`n1_s+lp%kP*{6s6xy9NnW*f7{kUw{^cTDcKe;7|3yUU^D zzv)uJxIcSJ%kbZo11O|(7yY;?bM$!JoSCsYf<;{GE~H*QRaaIyMt;U!sj!}tcZl;s zh3vesJ6A~QEMf1Cz6V53`r*sGl9<79Jj)IH=Y({gs5D|Ye=Fatw-U#^$2)Z+Bma1} zR=(TsIae;u7`B9xe5Pg})_Tks%=6=EI~j6~3n^^F&h!<|e1~4jSYIVX;(%nx)J5QV zQ!{7hnx7e!SJ-tfq$jjC?oc;tew?GjHd!6UalpryJxdgiJ(u zo_SW;tn$Io%Ms!abJerXmN4Qh`(EM_*Se)pj)=&2f5s{UA&t3SXR8uo{5P`v(FdF{ zGy?l5r_VN!9b$HwNa(Rl-JZc~Jz{VKzQM4FZ9|AGZpt6=Dw++1v$T@%ZTUT zi*!A%ju2X?O_vWOjXq4PdVY;nD$ z-Ns#z`r+FB8T8kf-L0$vt%*-nqOsAMp z6Ve8HZOd9`=A+3q~wENbl?r_8OIY#{^PswH3KGLTQ5E1%K2a>jZWA<=M* z$nK8b)Cyi6^y^n-5wHS6zmf+=zl8Wfe{XlSD7nGk@$^F~2Xe9RW67x5XeTZ?KM8wz zWVzVDSb&iJq3^uB=YpDP$vx(&lPlu>sI!*;nl>n;I$F0&zHY-9*XmIxif6QsADjuR z&%QD09`)=G`+szDV`3X3Hy5maUZZGF@oz?HPBY?#L^BPp!I063HNk82%u~MtfBDdg zUb4v-+AeFv7$)4g`8WRn5!12azljog{2e)37r8{&jA5O)r&AjE$J0LJ3duDk{V`L# z<~5F(Eh%Bnqpu|k5_@^piqWz727hsX{+lUj#lNHb$aij?$i5>Ir7*Y)I}`@K+b)GC zK>Q)-{|`J_b76!e6GJ*K8&pkX*^$Z zeXSL~3*Y>~0~p9c4{RMdp{$)xjbT)bw2QUW>r9CqJRw(CfuDB$A6@5hz@}3 z_gR=)G(#IuNSn;7o=LvpoqHuej1Seb!wPBKNnnL%Jhyzw0?c?zR zGp*>=5VYGX_Hs}vuXr*~wqGfSgg$Rw`m#_`tW;7${j(&gIkdpkTJ?+SvA&&rd>n#mu1Mb640|Cq!#1{OGaS8 z=-86c#CZX0N&WC$e^^#B=KC)f2z=}RzFRzRJH?Eh<(Fs9V@1f?BX^9otvx+lfyk~M zXDO^LtAJ(Ve&TSr1}7A*AzUXLd#TrPs~RPr_*-KscK(IPh#b#A`#?6%lfgTAj%7*LCZie?K!tp7=A*+nN*dymk!F z_4C|gyxaP+fPN%TM=HpBg|v@1eYIT7(OggB1^X+nx4kJcBBy1ng(YfAQf{azB zJL;)znFEFWZi?kgEw+3U?-31(MjB%t&G0)ol{+y-L$xPbgJ|U%{XKsrm zb*!5be^Pl~!Iq6-*pqDUm^M~i3o-7J=%b@HdfB!v>W946HF@X--Ja)-UcSIMoWy@!V&$!pz~Uw9&sK#Ayv8CpD~e+G6+}8NL@aH?%&)P6GG&5mF}F82h%u zv#|E$$F6xC7eLNCA zTdc7m?>*k<8+}424xe+1$gHf*PTZkmFAyOg5zphf9$0a3($K5oXL4vflk?(Qwms5OrdzQk;;lbeiPT%9~Z`1rQ@;d?q_q}*Blm>K6& z2x;71-!pzT)-N2dYRjZCgnV@OI_4FYxId-j$LG3;1n}yV7B9w6g`|%aElqEV=USY+ zqF3q^*@wt2#i~B$e2b}j@-ZXc#u$;Ae=63?9I@zyY~#pU;BIoqo2y{O!D&_t*210U zmG`(ht)-5V{IuRCR_l9oVPVCA&WAmg2koYSnTtXi`_hg$c|%XVCt?n?Wlnc;%b!VR ze5!hm6K4jUJ_7eN7m^-prasHWi0A1$4VF87+rX|)r+erdHAeTqGu~|p7w%Dbe@4_t z@Tp}lSYpIm&NEW&AwLjOxkuC)BNVKw7Get{YG?RV9DagTd4BL2y2F^4cD$MuEu%AI zJ}g6~efZYTEVb}?nQ|CcSLV7!f|#_ z2WPyUEh`lJ)d;c3I!mN)V;?3VHK0`>!vqX=GD(CK>Q^h-R7o=vm8sbRF zE#{-gCz|uK-Ns$-&w0`ec&=}hp2Ib`$#)MCAF_>__7({-{>)B1q5a$=e+i3kdSB&v zIp+oE*oef!v!{hL_TrqEB3C+{=ktT{*`D{k=ig~E4nGiC1^hm)&2Z=5tvn(3$~KF5 zgKx<3VQ$gCz6G=TudFtnJ$?0_3409OaOyyh^9?UX%={ISBi{O}J-8Te)DevrE8_j| zcJ$fZ=<~(+Gu10L5ovW}e}?3ZH*&|18}+7x?t+j!(erbYMZ=iU^~r@8N0n zWAx=_Z@fa%(vg8t(wn`#B(6c0C5}@0dVg3xZ@zL^BYiC-&ejsreysI;cJNOxG?JLy}>zWPPXpHq?X&B zRjer%Qof$;Rqn`tdrb>thqrrr6Q1j?r)FK!Uku0`?vT<%uc43{&;xi!ZyTA=9n0qd zBeK0e5F_Q61U>;Ie}dgV?;6E;PnS2~!F5|VKjui=$6VuQw#9%sC9<0{=LAbmMV`rd z_q2w+tnc1lmx9QRi_gvxNn-YNTForu&oK6STsJl`s(3|wH(JQ|db? z;Ux(X^a;Hje=<+vec81P?t0{?4-&2YM%kc+U+)P9CUbo_YIek_XRZp(A&BrTWNHL`VWNYml!!EVBV7>o#jsR z{s(vtnkTfQbZPx#cKZ{93>YgA(h}d(p1ryO5kGKvu7w=FCB2B);YsYJRm_BWUA70G z(O!*vbHBbL&qsgauo+{Njx4@ET`~#1Y;WZDae9a8V&SuC0Sr0$8=h}TI#`IeP9kKA zDr5G{fAf|J7?*jodhigDmI6jR-<)Nk2jhtX6y&{cNeS~wTu~pvHCW}tgCV38%sqbp zDZzL`*7SxOj5O{e^LA(%^U2>9ufkZt+g;ib_h}e~I?OyS**US$HyyF7~N^GC#Pd#k(hP zcOoHeqi^ut8w%enC#0AG{-lICGW4WoxZ3#XC|=CKd+!zOA7UYdxG3i8NJ{i==B;!N z#bwN$%AGt}LPjd&DG@T;BVVVGofGBIdTB?&YCH?ktP*oDf6qm1-9m~ImiTQcC8PRA zeAt&KX z0Pp)BA9*x|xSfMfP)@qUP#n>@28m>$f5sR|9tg39b47%7>0n&ZIx^O<3poutN(hlX zMRtkTPFJiC7E&bQ@!4AQDrrPcjKqW-aTAP@lK2KU*&qBFnPzAM3h7d{xC^~ktSS?- za^f}UHwDr%7DjB7DrtnsETJwtLQ3t#yoFS|q?v?_9EtA;ncP1_$dR0w{ z@oFQcYQbAH@l%WQRhkebq^D8v?95p!)s6|-+|kGpodcA;VvvJRh&P)c@0SG;VvN)N zkwSIILRv)2RmhNtL%Fj?e5_B`$Y z?A;{fc!@tDCnl^RbRV{Orwuxiu*av6+d8gW)WYH9T8nz@fDAvzls9#s7SifKNgiX*5w)g} zik=c7(@O;zX)NEO*Sk@sg(J11jS=IEm1=!Wd&6^+97zLhrkGMTv`m4K`bQ}%208nB$^TL z6U1bj;pwe0^UJ~Vk{*j7& zC$?;Zau$SC&xJw6=Q-fb67SAbCn02oOui>abgKAwZmg)YVwbK*yD|QYz3|R;FNMR^ z>AhgDBO%^(n7?i2N!=xdXir&+_3MWe5ixwq&ami1-KiId5NBz))}oe|rPvs0^BMKF z>J*8zID-fWuJzPMf5_MANHX-H#5r@JzSNG?kEfn19?I4dvg4qBw-#5DZp3@Ka=esJ zA*67%q>!31A67`Ki{gMnhNL2{+d9@*1Im)XsJxd~wnUW;7I&y0H)}_44Tp#eb6mGw zYeIG#_lh{zN{AN~<2@Diue{qwRET;d3o&cd^GZm|*o|IDf457$`W?}V@)LMRV*a+- zI-kU|c+P{66L2=3kehLevqjl5n>G6Nu_AbOZqA#}+m-FkyDj7mTu~XW@8fs6W%Miv zX&*Q14$kOsq!`uHdX%^g<}UZ-OtC48FO<$~@TJr!i`Nm(WcKExA)u`ppCl}GV-(`) zZ~U2_7BeAjf5D{6VMkh{u7W~lXw3aRcb_%W(YwEG+jw?TSawhYrm%Cw-4-IQjGu`f0;7{Cv!oTukQ5?Q z0?$E;Ru?j^6kEXFXF^uO3|2?-#@I)a0ccbeW75C@f9pbpl+Y-f&U`@pLaQL9Ws|bg zO?oy|l!-#h#xt67&e(ZbNO5-nBPmgCxR9PQRkI3_HHV%#S=%3WV=}Wv)kKbzMG;&f zG6B)wo6Ma>_-xkmLphr4W~@{f^6c_CD4{k!2eO$WMw3#}lA?S|rGHpCLRz6bRUz&_ z;3s&pe}r*G%d3Xo2pzq>*?cjF8*{YqKHAhh8a19%Ugqe`0~Er-gJ2=m{3mCVCdq7~w;@jNv9X zA}e*uVG{C6M_+)CF{o7GFT`p=8$C_84)LD)fDFDrv)0KcWQuH1Pi9AIiTs%!B4vR% z5-)Yo60*vnu1=Pg)HBObpZZ0aNKC ze@i>1!x~Ul+h7^8y+WNFgcU92+5(XEq4gDWjJlOCCk^UrTf5OE` zewOvKQ8(%%-mMD}u|cY%bVR}m&yG(fMK7B>22;kh_CBM?6Z5~<5bj*Q^-?vFWzUI1 zhLWw+NS*(MRB0xBQxoSz?VN3YLG|iOAkUZBQ=fhI07i!Le_HWT_W>c- zaIT2uT?_WWGj9Wn)8mEAR59Bz@0)Nco8{w98Aik#FmmS&Yc}ic^P-`RE~Gr_reOKT zRH&nbkQ6xfk-aq#`V;fbC18BPTO$Oj76M9bCF86L*koOcJRd=%nLl~%`t`~$5vx6B^qahYe_U>21-iE%Q6{T;E$$=$He zc&gk8#Qv~Co=<8dBp2O!+yz&{m@TZIcSjB)GWpRHEMXNZueConf9iVuHD*ud4m0Gv zW_nNRX(8mLiGF{#oDW9(tR(NVhKMzs^(SP6jJ!%ny_i*LW!8x{t(Da#YFaBh7R&|J zVQXC7Vbu+IlD$p_u^Wjag`;rhJuxv7X?4j6#amx*Db$@vTbf!x?QAk+E>+eE>9JG& zsgO|?_0jauvQPa*f8^A}XH;iuU{qd+4UA%2|H{l3Qn9YuyxHxbKJ`L++bk2i*H+-_ zj2JRnltL26T!qw*@kSfjTmK{CK~LQ6QOafMujMRg(@MM+oNOXw#)45&8#@(bw>EUM zG2UoX)CD!5L`awz|FPLq#Y`A;W{1q%R%|%ES4h$|#T8vXe|Qs5)oKB|%{t30T&Er%CuR z($mAt9y^jdf2`}zNs;fuc?l;(Ig+`m%&sUTpPlQaTX;`z3k4%7LNZQq7P5E}yof_+ z*dx~|MTzLuwRgN04UwA)Mw5iJhV?S`o?&k~9fFv!yRmuajDs=$5+pnoy#?1 zM`R%_9gIJc@3(>eoAuX5z0lC#uTHhO*>IjFOWPoTj3 zx6)#Hn2%J%(IW4UXHD>mwiYw;cp=$Po0T?YZ{wb})eh>P(nGmu|CH_nyl{28`_^%- zyB-^TcBRk6nPlS0k+aWAmvTf;y^!>>e27TPVJ4Z> z1@{2=GaU5pa{{|W8hZSNAt||48j|U~Jki_+XLaftFP9Y~=^dotP_KmS!@2nNEs;oN0M^CUwT6}n_MErRIf4Czb zk-M6fR}Uky6X7<#ad6JtWdTu;BKCKO{!z$?(kXO}w zl3~`K5bvMPNaIS|DlW+Kt$Lpa^p95Cbz+{X*GgVK*Ivzv5w$Awy`g`niV`=>w5X+X zzce9B@&%u*tX#1FPL=pKtZuJ0e~5eI@6?`gI=Ojo)_^tawQkwf_&!1^%TfJzMo(~+ z^%0Tp)FvApJ+!rX0s!tplbFZ2b8Wq{jZxdIlkZ$R@u8QkdaQt^2?=?D^So;p@kY+4 zO%M%Wqz1go8BE>862zjAY>iXzNoh(CM)V0Ii z)&Z%=ZiVm+J|V+9P$RP1e?nhc3n6#>Hb(sbto#;RM``WZ%!53NJwWwuXSjEVhXE&9M+M3Q(2W@5I~bHa+m zrvDCDEh!{l%sOgba)5T05O<*?azAQ)Tl$I&X-B0`>6Wx?>0s`0f0MZ|sbi>+Rt9P& z$x4fu^(v(qI8&*4#2#6V*SlSe&o$4`apBek(3ck^jJbp69E)q?M{lh?Q)qSe9Nl_uKD7Nxg&FqS*JTR<;$+bdnPD20@SjH`(!Zmh3w z;)w&T0&Qq9tQ2e~7sF^$b8d<3){9vO^Qt33itN^rioOjYvh{EcjM5wtbit>TclI@hN`@)I;sI6PH%hRNLWuCmed)2?nQ z);G2*n-+a(GImQqYqz}^9^c>>qhVaLJ*V;f8EGuOKhIlx;QPGxkyY8m{U7ho+X?3? zI%4b>Ywt)k@Du1HVugPXDW(skB%CWRq}sqr!5(IKe;8dBQm|9L5D7T+9{2D>x@Z@A z4AIahBt&|f8G+R=mZy2lY3#twU&>GAu-6FYhdg!q^RL|zCORqWpJ5N%o^ zHKEtAr{EL$=F3-XHWkSJ0p!fb3N%F#!Gs-b>Xd-EoIziq30U8983Ul4N9GwMMTV1z>-zr+% zJqeWJR@~a4Emqv!-Cfe60fM`QV#VFHrMMUOV8J!GwcLF7ADnUa7<;d^=9=@lx!%mO z6y-wDi%LXbB`{OxSeS9~Xk;RHu=?Tc-SXRXf}P0BFK@4TkMj6uN5+NPzYL~rvlhC^ z6&Q^PxOEx%kFqC}X<-Pr#(VIzH#Hk%=wsU^-N^k=-7R*4luICvn+okvR>F-^%7|sG zXsbD4;K+Z@=5dYYV^TM&Iuza~o%3OcA%ynQp6S6~A2VKHV*aB8h@|J83Ox(eQgLAN zXwDPuV!A7+kW!C5(4gH$^_o(xk3B>}og%%Dt|l_a#9yuK#PQID%zN+%Ji;6*4KeqTm61-9_f!T|Ca z7&Y^6^`|Eg(8okUZ^dQVLT?TJ19#K|&kwuQSN7(cN=y+BE4zd~%uPjYWQD|mzQYGayxjqxqeJ?gLn+xr1eh3=!p($)hA2OU)6@N+p!4CJQP zw+Dd$DC--T!BJj;5F@9)wVffXccyEs7@)fzebq@12>>E5xXbaS07(=E8I^%WbHSjO++V(zFC`)^n_y(yC1b4p*s{k=COL=_ z=76J8#miXK{bXGUPFJb70n&fz@}*A9?%;>GoxMpRKVgeUiwY*%)kIY5$&bIXp8)yL zB*8E!BfyuzgkFiR#$;GcLvx>H{5{GX^={k_BWTaaGX|8n+AH)g< zk~nU?_4wy>B z#nwN?T!wI7*M8boH0;AqD7h1yX=>Q5DADy|joJ6-e~IXU;jhIKJ|%^B(V*f*80#U3V^o*yt&Q)5b6+ zjPz!1?o(J1V4*ZHq_v=rlvurAX|Jpzv37?=fnBj|djIyVVHzg}Jki$JsQNLVNvq85 zuy1fbfR-w&%1v$Fgy|eBUbJ=bXKPc5sbozTAIO`7p%wK{@}dEF2bDrt2$_YgWl)cCDIQZml(=quX*gqeg^2&{c zL%jZybaE^VIE=4b-a%*lxOc6`PsRoaD7Q_-`!BIng^CsGj?Mo~WEa!HFcnWnL%fCz zSU6J6Mx;$qPF&^L?LhY`*Xtbvx%tqaW4ToGei4Ir&hGwOrX5%9{r%^T&DVce)AiH7 zEl!_!nl=uvw6izt(k$AfjRt~u{r%K-AXQXgs^!d!25z*SI_Zt9?@orLX@5-oy-rsS zt%&{Z#W05Y|JGN^s3jgrz)S1PJ_UiZUId zMz%$v;N0WM9*aBkm8~he29iQxZ4SNUc5M!{GU`s#(R;=K`S__;%`uz2Ch-K*>g$7z zi@?hY#po23oLPZ+quC8=jFr%SCiSMb z*Um+b%FRtOg1mrhBygD`#kdZKPFBXlpLn8^WJ>7%=TU0dqu(Oi%AMQLz0RW}?+=lk zXpY*+P|c+gh(5?9Ffx9QJH+#E^nz<;c@>r{U2i4Z$f0I5{_)3N>Npg!$3*2viyvPN z6roO7h_JRwli0;X%^Dta$HrvA^WIqt{@NAW<%=6a->`?O3Zsi2q5FeQ&rx#y&3W2- zR_$D2;GsuoyKQyXd0*MZtoK4my_Tfc?Y~QPu8~_ph`JnpKUJeZ!_wSB#)Ehz%0hLM zs^wc0e42^q;SX1n7A7Gj(rp*pPdNB;5{lGoyA2+bRph`dtch;QjE7g4r7PmzksDl>`c?Jz zQYu3t%x?4Ho&kSYyGkkkZLJaZIH!9y^q#eHTCXMpt*$HdUwChms7WlWch;KBg*N-W z38)Wsu|8tj3A`=stHwCJ7G3ZR#^m*a zXeGn{ri-+ScYWn&5tXD|xuu+*V2DN--N#7)0E>T?+bkbl3Vr;8h-EjLvF0K zAOi`}2;hF7fbTA&2hq~rW{R!(`0wi_Fl^+gu2;=PW(1|230*O8C7P`Qa8(qn@b7+2 zXgBP1X6v$bM47~lrREz`R4!<`CEq~gtmkW%?B|onPqP>NJ_xjAc}I5>PmKZwgboptYy-a?g4-ueSm*$2&f88eVVSFW13v=8EoyU$ox@{1I3V^vhrY(KmLr zpQ@?mI#qxFHN3G}opwpYtA>r%F4&SlOJDsP2CuW@F>#k>9g?ujI^e*#m48%0k3 zKe3xt*#4owzcei+;U$G{qI=fDT8nlH^S51V;aYPGsQzYK<*{C7=-y{Rn5=^#rI@ce z^wB#Y*ziR;{t*XGglk%-3fjTmw}^24>e>}(MiG!3^}J?08$^ECa!pxUP~AGA{0wx} z#^Wyiqb+b9Q&#!rSnJMl4Z*uMK0jcx9z!a(BQ-#n{Y@O=N@ne@FjM#DkP)Ynmlxd4ir6a1 zRV7`&rLaCq6Smd=DBx4aj%kG%7l%MHpJlL>NVwE-ii~nTCFk;(c^NcfUbV!woPV%f zQ?GDoMzR#Rou-2z#B3U|Iv&470`A&2&Wy{M+?$6>IV>*U#rZgI1{$g?P*{pyX~9q2 z4(xHtl?+4*OdE%IE`kW9WexX^>2ljfAC^4$QT2;s_~pkcBXn1?g?-;}W#qT_YIVGK z+ODR>aKh(gssJk8LdQ|3>s1FJCj>^I&abGIX>AjZvaMQ(4eVuZm%Ho?PgTa&j%y-baU5TlstV+*;A!L8@ONGGTEUX`RJK(LXhQ z$AUT;g-6(IaaB@}vA@7 zio-hEwC9kGpG`?|ppU(kyeA=s`f`C?_4?uzAPKiCDy!`{=;KB`LtEFEVx$8(c%X5P zDZ55NU!JYx-0KnBLe$S~zXKoL-*{!fr>9K23h-rMJ#cDI!Hs%f14{SV-JSl=ce`@| zJor9NBVt&{6}UkCSJ6N}1|S-y%nbK;>!Dh5hZviCo?Uzn?5H;Wo8A*-1!um^sX<>gkcj+9*uv082;8Kub+~s2EgfQi>hK?Npk)4Mgld3^ zKW{$h`t=|OwZ8=L9+~_YDVKT;@7KQ>$UZGs&%DKAU+!0oB1l`4E)1}Us@83x&~2Hk z$kTSIjEK=)JLFU65#7O50d=+QNgl=KKzdV(zz*iP=SvD==9f)>h^|T2Qu8-*JhQ~C zM|}|2c3_1dnJ+ra$ZHy3i8gdX2=UG}5w=z$)=F7l@Pk74kz zRxBed$k5SFE4tOZI5eS^ZTB)iE=U|N`icZ>5KNL_LGL|8Qogb5u(QO|=2kocaXhQn zeYdByx;QeMCUPX$D>0c)Rv+`U4v3DV3Ahu7VUv_BX?H z@bAmljggiQq|dDt;mPudI~>}8(1|Za?QyNR2donVCsqZ(P9<0uiZ?$}Gq55V-XGO; zG|R$q85!weZKA*Vt+anUnuhKV-_`*aItxg;G8#5mWwTE&))^(TCKN5~EUwRKnU8v7YM*QAZ z^S(uA`c}`q51?P&#`qE`dK_P@K3pFeF!)@>(6@HTweaR5zslq28&M)?$AH$?=OFBT zEO+N#CVK<1aW=rb_oy#NxCfQ*uhin?M&1%~T10|<5Mk3peR$9{gG@U!Zf7!?`O@?R zy1wi^^@WGVYsJTPt!zW)lU99opKASfhGNz+*V#}7ZP!Csoj!b>K*%%Colu?iz~j$n zCA}zvW6X=M;0_6z1dd%;uE~6Cps>VYsabA9izAKAmhl+S3h?Y}VY0^J1)c0(uv?d?f%0H?ag&gMo8){Scj{dN`h66*%BT98d)e zna!&94X1U4p6tX3PEU)P82*_LsN$^pSU>2=KjCj)8zDt*vQdTwNZIuer`P%A9m)1z zU`4m$Q5F_Uc;i6zaeoeT_5Kg37(76!Iqm#m_v;;g;~R`$o`*^ujnFR-1PDrB#)oOdzUiWs^b0iX z=sf!iviQ!ryDTG12$|r#;x1ooq=7AVc5W+fsOK^~kw6?bKcUX_g^Y~DPA>-;O>+)J zSBWIE=4Z@PrNP2Q#ydEBSH_CC^6c)I73g_klF)+Tmv<)MH z55Fy}OC9&mGm=;aF62N;<=1-cl5S*S4)}<GIn5%C|1T?G>6xDG4mL61n=fM1^qYXY`FAc|)G4pHHO(0B&obeGtXo zv4bpWWzu0QZST_5uMq5CMwR8uLh5S4Zm_~f$fkUcPkub`XN;sd;jX>NCf)x6>!Ro} zb$yc}*n1B@xSS^Iuv!X*&R8Vd6Psz-oYxcRx|zKLx7mLLxFIh>WXkA5b2Pm5rBVdt zJm1CYI!X?553#bVt^AK8OT1=TtPJQ#Oa8ss2KkQ9 z(AiIflz{V|(hKxT&F{ZpyZkaTz(6JB6#%H2#c`)ttF96izl4{YYwLFTCu%J3AtJ%{ z+W|DpM~Sm*`p&Kpru2rbZ@}MU|Gipc0Vp5wn32KUXNrzziWJ8}wb`s_+Bbhpq6{6p zPoih<4P!=?dPhY+`T_X!ms;ahMc0b`?UL4s9muQu+}o~#Y|5(Q)1J#**bzV?$2Hit zp#n5jkqE0KlwS0!VyYn zawKl4xY(6y=`yL2UROZ>Fs?=1fab+Kl_rlP{4wVKEL$CCA3ftLu?LPLWWZ?KL z6E*Oz+bp%#Y@#fl=rzJBmZkwRbu{J&tSEcAaXL)&VVt#@osY5s9qq9Yo(4P&6D8G=|q`<6vA#mFUyDR`id9OHW z1@p<36&3k-t9+ai()1ZRFHJaQ@#y=;V~Bm2wymmQkEzP@&}_lRJG07q*hR=J4%h&O z(`J4ti8hjuM`&!rkZzS9J8TuyyUHIdg*XjmY#ha{wE-wTP{8gyi((CrCS2_o9;N(Zmgf zSr4!>OCg#1*3_!}q!u7;*BZgs-zv%X;2|)qZ_? z9ZNFPM82uYvf|wA9#IreN!9;+bJfe|r#O9Qxbp)kwT323;cU!`WYn7=?g&90cj-aJ z2)}_)pMkfKs%24I)t9TCpZ?PMJ)dkAiC^`@Ob(37e$){*z%`*LAaF z{ssD$k%}MmBW-<2Xsaa|TCZK>3D8SEe5Kkuyo6gW6MRd?LoI z1=hXU3nY)3@Zjzez`u6&?xgGor*He7P~UyVzI!a-0pdMy{~H^ zeS%T`sV7X|H3FtZjn48SRFy`@TTyl7OBKJo(6#NjM$M?h$<}!5EoV}QHq{X>t2;Gc zS0u-nUPAUSD}Yap)Pi*%0ls;XX{oax+Z-eyGVi%yvW2tT_M5tApQ;B87(R*vpC$$>pOOOO!t5+UZ+X{=eMb0Y zZKieG!teMLBBviJJdUpKwl$pr%uan(W1}IpbquupX#J% zaJW|%)Zrw!$xaWL5YRi%Haqlba)*@Brvi&pg_oHNT@96#L9Q11P#yXz&rClt5hn0w zX;IeR=Q}!e0i~ZCzsDlK@=N;7no)aDxr^+ZT8I|U*Ff5>9Lqnka)J{TjDd;v-R;uV z-_5MOuTmvrPj(#|O&%F`ZC?rdjtd%$V(#7JlsrF_?_O+;eLrJBf=1K-6^1x^|I8+^ zb-WmILvEG&ZC?c-C*RA)nB!J0s)ZAj3<$_}x zeCs1Qc2RsAlxg%_(zAYa1;W(4jVA_d*BH$mcEuAimX)81vn zM69KRL~H@yXaPjOmn9MSZqBT#Uf67kws`j|N6a{fX6tdo(+5V#3w$N0xKIb9WtAcR z)|uXgAYMC0T0Q_DdqwrIJ4USU$iOiEAw&J>dO7or0x=6Gc*)$A39Q$Oy(Y|D?7LY5{ zF_-WwJ7Iqmr_(on6c<_9`(`=sDOb%flD6pZcLI-;uN>U5PM=_&^LWM+4(DN^r zaHV>2WqcMFgtyA<9G@pkqhl$wA(6ILrt=)!R3+QSuQ1&iif;H|>TkkY-ttL!mea2a zy8YUeQR!%?5{&p+9OF_8ENf8bL}V8Er@iUQJyGXA!@5t9>dPgZXULJ2BPvVTZ+S(3 z6(x_0@0%a5Qybd?Tl(^>@&3$q#)8ZYi&^;MC*EGI9Q0_uX5ki6V^fMJ zA!?kvCG&E8<}Pfhk6nmfVl?>m@rT~^Wx;)ziDeVc>=uo!uQ9*HrKQ0y@1!U$M5KDQZ!AL3Nv>vxM|5vgvCl5TO_Gxn-7mS%7!Yn3!a)3KO;T zpR^vf4RM&pby+9M96(*s9Y@yb$n{QAhjRB4!jRBkOt8p`6g6V(xeBN*kR1>9lxeqCQKpEM%lpC?*4{m59*=OYnh95@3be3aS60S{ z^mD=d_P$4b*pP5>B&>LjsubeB*X-PR%_lIcLZIjN*B-|)C*T~6T4b*Q)`7@s z?7#>wWK}e)c9J=Gwt`EJQOo`}JY$$D?0RlO=w6V!2NUDyaVN71`0?Vz0G* zo+2zygX^$Y=YylL_mr1Mp=Za~m5=6LqXzy@S^#cjK{Vg%}xL}51g&jgt*BXH&uCmij=8W z+4ws;Wohm4M#Tj4;{EPz&eSEaFDiH$+Ly@0+K_zs=ISTt<>=q#E0~>5yV}Gke!;-i zO6Y}3SVy|A=lDe{UFZq{ofkv{E8zhqitHEg@pLLno6!wB8l~+7v(7uV&?rcA6}P3o zn>(2&SF^NdxoN4@W{9)Qj|22R-?zbLsVlghsSZMin$5pZE+@=y4oTs@+Mh8GQih5* z$diHfeC+k0d8U;SCD9z{>x0V944cK#G^}=I$VcMel7o=@!uI6yN64Ww&l0dw9FUJd z*B`d^61nk&#IpKhe9tLjYBaFRX>{Cr|MF)y>X7s-jjCL{R-TMAB+!X{qUC{m&}~i?;a*2tQM_ z^Cy(`IOoCYB>ydqLTJssw6uiM0}0JYw={S1=qrK+wNv`;jZP_+(34`mh_&C&3midl z^Y*43v8_L&CBDZ;EnOq{Y3Te0jf^`cS#fH=uCDJqyeQ8dyE3d<>P0Fz!cMYUX|TK0 z0XZ{QVS5uBfp!w&IKmbC&W>t&q`Y8jg$-=JJ*5neNy~qukTH7Z1*4#a6c+Ncf}TD- zY*G;KoJCSiG-4&~?soJIwf#+HCq+VK7RCXOm9^n6iMYj~phU%Z%|AOSI<7imJS~i+ znqal@j8tU!k_Qc_Gd zY`6yOGq;aH1lHR+fq)Y_=m?=?EhL0DCQO}%{`s0nk_ zgK)qw?aRoBGlXwlr;fXw^V~P#&;BFZZwVGFr$=8y0L7yZ-x{PdcfbK%IPk<>as1E* z0!y-e|9~p4r*bl=yh55OHuECuI8{l7op;_M;*0pJZMy^iZ5!+N5=0wKY*EJ|AhkHYRtp>q(<2lUE7^%aP%yfLDzsnpF9y`DL&O8 znNnL0;&mD*wv2Zah-phLY^*T9I|E@@*`GmvGPr!Fe|@6!2+xi-^=h?TB!JCge!_A7 zmDT%n;|Ma%g%Q@>8@0pN5b8gJe&KA{v=o5V{m*MchvMH2kAq- z9_7Btng*N7TRN4R@)|QwKlfF+ivOI?GjZ)dLo#np^U|gda4F08^$fRx)DDPm(Tzf5 zK}ELE%gdVQDq4ehC)AuI#5R~eUjCveEMF0ClXYMl3s5tn6K(ct6R-}I4q zDJ=U}P$a=BWea})wC=Cj3>NKCu?CiFEf?nTM2vooEbr$g%K1toLfty@D)bK~oZ{%U z-kIf(oO3%$IBfNnZ-4*JZVAZ6|6jujS$6Q2?$GTLj14EAW)nzTy+en2jkCs4A{Q;q#UO>W!qyIJ z1tRuKQz=Uqi2f?qL5)6+wdNKe|e0{q0>Q)fSPqASx{k z5w{xFiq3S-Q+tm8V z{t3BZ;^m!+$AphXmi~t%KtV1s$<<^q;r)e7tpI!^C7$g;3ZOYol4{eMMO%CbYHf&1F zFnkL8&-Lfuwy!A1=5?xb=pI~#ZO(IyrioMB|CS#x%$XKkY&xrcDw0>HzM*yz{Wo`a5_`v9@aoo#R^-`+vIo7%U-?2-UBXePbr zmNz#pQtYzSs*Mzv3e1p!Wnc4y<%-MzzGrv3%RL6rj>mWqRw=#zfG?NZQkt9@BTymVAX1EX} z{KXErES&Sh*TK54*ekI#6^4{|y}{Qew^Jpda%M z^9LA@!tdb(9;20j2_@#77AZ09uaql1{*c~%WbEyXskD{eqw=m(hPHKBhwn{QW^07Y zps`I!h0xp^p~DqI@&DlWS7y7^h9}p3={E}!^;wczi7L0s&HF1C#_b4K#lhO_JI7o{ zYEf{nK9xOfjJvrEI>pG~(qN#`Aj5T4HD;}}#m$0k=K{tZByJR4VJVK7;SaI@3-Q{D zx~4SytYa$Hh(+Y5&eyU7IF6?e7Ej>Q;e`?6pdy>0mo7YY3=k&f3JL-<9M2#tp>2~` zxBhg_>j|Z_;DOsK7}G1*ca_?`uR*Dxtt~!L@uPrj)Np|XlG1v^iip0Sb+S}@_(I33 zZg6#d`pTOd2tpQYUNG~2TeatdSgVp7*rO05r!#D(@vF-7eIKj+Qf*$@b6cUlg|rYW zR1zO|vvU}X&a2dpe_fTX$0cWAD2yU1xZyDjeC@bdA+9tUtAy$e7IW3A{SW4=f|8a} zmNgn7HLm#J7R)2^05|R6QF~Sm(pUc1OTt*BO z!FBM?oLKI&w&x7F`Su>3cqJwAFLOdriW-QwdYkRXgxhatg@0#O{JlhBhqHCjU37sN zpr%fBdRyflOt(z~u&M}3{YjGUixU~*z3GA_6YyMEZzbz}9|yCS_nyYxY$$rj**no> z*DL>*=CsG2czcAfvs4%|67gegCU2`FBj~3C=|4IEM(cUD$f}UK%ecC5v*_-X>n~`B z+nv7_D@}Wf=T&VS%mR)OO^aSdS+mnu&}5X$X;J3b7eX^0Sw5@AdyXI13X8?~x5&N* z-I*nuoDd5u1A0YHZv!O{hIX-H*meU0wASkbEmC=&3#r(Lc%MS{9ZlW`z;h(M9$RCI zSCt8Cqp1=ve)!es`P{EtqqP+ctweb9@~<*&Ih_sPs3dP#(AZ(%X$$@d`YFBKO*w^4 zp50k^-opJGovd=4`^FxXaG%V^RsuwdI$odlLD8YWLp5--u=o}$r?3SfH`akh9(BXb z8!?n!n02_|I)o@1v0&T2S@P`ATT3hZn~>OElK_Q%!F6tX@lD zC6ZLYl3l8s?iqmTLjSClvLt6i?ez=q{P=#J2o zX$6(q?{;bS!m|pTUFK0kEu}{D)qb~5ii%`IPU7IjuA(R6L+{jO*9OgzPJGx;4tu!2 zNpG<|PyW;Gj6hFcNqf8yPk*BWbJYQPBC}Og8tt+DisldRD0at;3WeW+Ce@Cx;3~2P ze~M4r>k!(G2BA0RD&>_k2@#mK80CU8Xz z&vB&rY8R1H%ZfKa3E1kOy06DEa6X|+XKR%7sMZm>O?5C&BWWFWBo|Jm&|ib}88GG$ z9xnK|Dv-bVb)$|xO37u9kB-up9(&HG2a}kaHDF{T&n74jmG*BZ9c3NB&hk%fHRF~2 z$7Xxue7yCldN<>7vf{G>qCQByN==R z{Q)&;GS(G}3!VDCRBwI5=OiWE&wnl4pNu=9_&^X3g_8}bdRG~_`ri{js!tah8wPBX z=Jgr?=6y@>ZceK%87GA;V-fW&k#_OUMY3E+z3c^0=kE0hu`IheZ7n2Ti$}k9H|EvD zuj{C=L+@+WHhb4Msey`C3-&s_*`e{*U-)fCX0ry@u9qD1nh!B2j+YNpemU#I32!=2 zbNIaZoelLW9_zfP7k5tgzg_v?c#-lnAkTl$-81>G{rO|REN4yKDJ87#dOk$thHh(V zYr|XK9dS7 zWAcLE*6rG2FW0W41!_-hB|B;V6`kvltw~nT4iuwu8fvSoero%=eGe$5s4-HM4;thM zdp~z2)XjX1x*1FZXZe_oit%hn*V-KEir2QSume}Av|i+t@c)8fS0^FAY%WU-d0x19 zCN{fGWr=>8!7DIk=3m|ugunj`=G@8e!4fYhzz{0I_jluZvDiPv$6WC!?mHry-|2o% z7=~nS5&pg{+BYJF7WR(l?z;yt(oJOAan+(J79}JCseCs}H&ZDLg+28?oInng0_^E< zBK<2vk-?f8UIGY$>lF)zBp4*KE^Nj;MNouMTV%77-4;3ap!Nt%i`kx%p$oC?*i>WgK{GC$zuJT z$n@goL0>+A<#((7eP-9)f_SxBoLk)azu-gO&l!X8aRP`x81eukr?YAcm4>TrPEo!B zEnE8y?PI7Bd#z#GlHbKbL$ptL%nYL4cQEP6ybcJtQ{NVzdn|y!QH;ByD}pJWLM ze6soza0rtb>Es%})1GvQxo~uHm1TgB!9cr`d!! z_Y}KFVzq8a1K;I6KyFqq?HR|4fv?`L)*?qMgXaC`(pf&%K7*zchP4;R0nMq&KX-@y zpTI55Phxk=0WZ2pu(kGZBAP!)}n}H^;e}?z;5`~xe^rGXYR1o|AnvY z^(uwu=RqKrZMgNraKbwsb1(C1;41eUihBYh;l3Z_-Y%C}(U%3!w?nvZI13>jO`MJ+U1!AE zztu;b{4337dmH01t``j0YE1)nGsp4!L#ynm>%;g*2rnAu?JMpavI4$Td`a*esgMYC z46B}CA1O)l>@255^P2i;=bImNe?84j%jMpt6btf5`S@c+On6nB@wd3G7R#R9T$-fQ zwg)zRHHsxzruf+v*f#<>t*rj-9=JVH6sKRm_B+Watl`(q3TB3VX;^Yh#-=Ee_B=CI z1E?QWJGG-p@zCS{b1U$Y)#xr0!+g>^_1xv82xk=6D7SSxYU`doLlB|K_E1PKyVQ-@ z1|n~q?kffV8Vrv2;hyu=E;sP@pRWt!98I~>Zk!$v zEPgazj4WKLy+F}%#rM(zktQ7D;%$n5a*3yWe($FAi1YAyY&W@Xr^Lr8djj}TSY6$u ze1t`ljnVd*TmcGB}C_!b1;a?l$km0 zKZUk*7g8fu;N-1>x+CG%FqiKkZ{>u7xOK%3f1}DUJv3*x7r|vBCgreinn2q-d<})qW zm9t|W2SLpU@&t5{ha#is>b#|?%6f5l&~jf?YQ$ocH6J=lKG%`n``h3U@s?S&oC1Zv zUuPezO$e+8?!UUn&VC@60wQp#AC9~N3d|;{`DmPBzdYYLD^jOR2KJQyb|JNThgH6h zP>kHIz_$HXxiC6e1AiRLq(}1;g)J&wwtwhDWW;nY0yS7WN!d3$|7u;vD#_}Bo$gYF z=vl=vdFI#y+1eM_Kvn@ShKs~?9M84zU)Z}MrdDA*$-AvI5;10nD@jkiL%K3|jz6bR zSYCt?Xay81{2Ac&0$w35wN1-qVUleCjVqExA|qOxyc1Cxf4mJi!aMK*`)6GeRof8s z($MpAjZRSbSM-~rH&D_};IAY66mY`pmv`!yN^-MTX=zHfVd(zNONSf_I$58F2g6;1;HW4UKfryihTzhQ{lDktzz) zG5_A)o8V@{+}30%Zjq4LEbM3&5)08dnjzSX^mr6^m7AgRW)vT$6gHzMwlNxajeLVm zc=h8pi-1Q{glR0S4Y;o?qBaofegFlWlIE932D}BZ<@rUZ#Eg}kZhDH7MHV%#4>79s zoJZwq-7b3gk)Z_TGF3Te2+Nae33)x!&Tfus_AIc3Ym-QcH`(9Z*rXQA=tAzuJ}YKS zsIXK$3mvrimOlX28YJm89JMlo=F+otZYjD(X-o%5PzJ~@AZ1~Gd+ z!=WryCDR{-mE@g55$`0j;uQqj;!~LW{)W-O|FR(8W|JSt+iR3EBDa;B>nEU!Nn(+b zZazw@X{ICdb)z`{=;(Kq_#uk1GoM|t0n-J`u=Gh<_FhYRR40G6)fTf4C21zjo!+az z)iQt4Q2PM>`rs0{%BUQ71i{7Ez{v7BBhO`;sNp?3V@HhiT z!niRrc(%|Y5-%soGm=eEyX(LMOt6d`9k=!4sK|QwP{#&;=Tl4orCi7J{}RY0#!Jck z)gd(?x*l1JJtE`;><^3ni9DhMdzqP)C(&(Jb74r%45;Q-eA^h0ulW&BVCtkjIOA$! z!JiFUhran>+O37~CZb_(u4YWp2uY{fy|J9FZS1MaKGAUY{bUyL84XYZL0A)`=@; z3_$#h``u6n>f{XqH$AKIn|w?_*CDfrJf#7khjg1D~@JvQ*NwEL^qkL)PF5 zfwE*$ULw1%qH49{{IPQ$^&WL*b$%+~~WWDpRVv{E{ zYxR`C(R%)1HEGv-F9=ZYB7_Ummn&$B{FAsSz;%k|M8{AS#ad`I5p8bX-}-mZ=7<=C z2E22@S>#urZHb?Ljo7PQ)A^KJ-ADMlo1&0F(h%BW9K6Qok!4o#wrzoaxeS$?9SWVF z+3JG8dtjAM2FEB4KK#Q{>`C#6gw~cQTzZM+1A%y~zM`bDCOgA92D>%|Hr5$5=c2Hr z9aOazDqlr$;nh2PP2g%4a_Z-uYosuUVw}KG!`IBhtc9|Qg1va7zKg1{^(S;}JgBXP zgUoJkWhY1jf2UkbkzT57acrwv1``Ge*;r1j=xgd=VKwc(IqH7HoayF)7mVk&k6WV4bkf& zv7)9J`fS=5=FIG?M5gHhwQu1SsPu|j71_QGJBs@JWlY!kLu|5I=^Icn%dAdGu@1C? z0<@U(CBTj+U}u=6x~9}mb}%;X2R)|no5t%70ab5;Rh-QZk+K&2&p0s+e4*)_@0U=e zmo4&X-+k4otqQYlKzL;xL~ec4XOQETO6jJ&?S$$`6~p>~pnwC`t>D@i4f~V9JGJ8q z15OK_;w4M~+sm?Bk~+scna>Df70(=w#{I1p0%3Yeupz|M^il`!TyBPYpI3>3Ug_}9j0yi&5cpX8ki<&NaDD1?;^tzdT1Xn~F=bq8}U0+UC(@k1f=45XRF4Oj+k zWNZ!sy2qEo!W}?wI)(qiH?|C1w|kw)XFp@0Y)&3$aff06N4<5$a?N1 zcHyUHc4utI)1yk0LdG#o$?L0*xy2K9-2{ATtBeCX8WdqJ^fEF2<_ACPI3WS=_NR4; z1pvl@A=xG&+X_lwP~<)nV*9ku3#@7iJ#~y*N!VbH`(ALGeZ2+OlnbC?1TgRRS#Lo5 zlI(Io{3bhq)pgnjXfIg}Hsfkx&)8Gtp}h+;qEC+j?D4`JPnW&Xs0^E3QIJim`s@_id3KSXgc-YrUW$EWos z5O%c0x>xJ-+Si;>$iJC#&IEHP&4W3NraBAM+p|GoI?O|U^qg!8d#(}(CEV8_A&jSA zIJR0h?OCScQE2uQ9IqpKFDcxbigzrC_Wcyq#ZDOB&e%3a7BfCn;9T}@k43)Z&Zmv= z^#EX%-v~Or zp0FI6N1i6^7o70RNhi4|jYQ{M{?#Xo4zl5Jzj_W_iPQ^?u5GAzrKHihU+Xg$?~osi zwQD5un@SKIwLt?0oHTCO`B(G!(YFpAnT+!bqC{X0qQMYsYq0Wf-Hce0><$)W)XQ}| z+iHylHy7jRHTE7y5mb(7VS$}w^)H~I;A zl5_JYo{!KONqulb7b;%5I{J)-`D$ID^Gg@)EBmD5y?2ECV_}M1d{W4r@tYN&yo=b} zZmON7I>^m&B07(h_x#pj8l79+5}F)dyC>$XuM@}EEA%{Fdob?bc%pK$3a#t@8#vmhmmrgW2z_yjH8-3AXFxKGAtmPe(7MHKK-4brz>|ufX{tQr z^Nein`^y@W{pZ*A=Tv8799{+A5N*?>uW|=;JDy=cb6f{X60}%(hxztZ`Zo=-awq@ghZl}SnQsyDhPprW-Ph;EAF0S(CH> zWzz`MV4ySJ-I*(Gk0?=iz-L(PTB*D9i8!a=A8Wymj3F^N+?dJuNCK9KA{5O(M@jQz z+LWU3de**s^$YVk=mbVlUQ?3V9w5Y5e{m8)(}?^};%H(XQuWrs;h%DbT>5UV6*l3e z08e7PvAUlGeT*nJf8{E>Y15d`P#Yzwj}fkg-DeYf_%k*U%+gX2c1d@y=%piAVD<*d z_u~9!2~`FAu7o(%UJ?E7A3DR#YxnFj_Rp|gp0yj@HZCzHLE-w%dt1AoxjI2q{3{s^xEqTtjp+g> z35^@z1IE4&E+%l5EZXuEFTpixA>yh(<7q6HDrJ#g7TQer=zRrf$Jv+!!?~kCI7w7OdzUO ztccr)CJvN;TJE@-)Jjrl zRUAahtaCUT=)+b^1GfA-3624h%dc85Ig$x ztbXR0q2`hl_pOPleG5I%aJ?KK+ zM#$u!69GlVvK&fan?81yQ6uW+Rm4bci~HnA82!M;Rt-zmFYbb^uYcoH(bsX@ci6lW zFdIC3{@%Lq3SaKD%b|vNSv@L>L<(;*(*VWz{>Zs54iSxqJk8VuOU#oNUFEH~5M6&N zR1-1`$CQ|q?CO=uId;{nKLZ=Si?3}_mOK$DE(=t5h!2f+s%>>!*E6 z*o$7u+#E;^IL=7hXEON!648KJJ8aKb=gBBkrm-OJkuN_Y_W9-}Vz1kRbBI zJ^MP}SHy&X-?e@+S0CQMUB-Auf9E&hHq+e8WdPm(9h;DJK(kA zbTh}lJ#j4%_oFGO2mB-gf?h15_Iw+(S-btpF>}D(txF+p44XgJq4}GrbZh?nZ4jb*K z{EQbtFewK!so70L+*WQx(moMISMI8{>B)(yU+QyRp+xf-(7oQ9ZQ963Z)1ESCe;-6 zO;b)4@gCG@zxk`orcPz~@ z#6o_B^?ppZ_0eQWLdb#b4{t?_b_^oYbTEq4K{sIcGx4bHxAP3-6qz@cVMo{lc=T%Q z?(xl}scD$-mi_v$fjzzySqrw@CR@vij>#^LxNX(#MsU(0BSWAD}gm)b_%z7`reRk!cX|0JuJ;86hE$r0i<%WSki!-L87f*)7h zd1#&;`n}MVao;o-ZhTPvfFQVT7nr5L{P#z#)}rG!l-hV|eM|5bPkD@niEKxxQhVQG z1^r1YSJ@t36_74uiTD{X%d=v$yi=6F`kGmzez;?|XwY;O<JeIqKU#B*k5KcXqJ9z{_uqU?K>?kJB)ea!Rv`quDzN#*B`QGdE`dV@5c2` z$z142+8w)OZAS4u%y2d$&?+O6)e3UChIw6D@u*2hX5Po!9N+w` z51i8f343AzpAX!Y%;nFvGq}qRwyJ|t_&8>o@QHhbu0E0jaE7*c&UeAf4N@Gni=AE~ z7B`-&f=-A32z%-T!eH8NWs3iHuucQw#%X({{FR3v1Jrq!j8?{9i*jl~QZju`&cw=_ zi_ZC7b<-1Q4U!d<9Q0Kt#v@Uz$fxl8Gbue3w(WSovL9jci%As*b=x#UG3?4pZdZbe zh;y~>SR?_l{9h~jxaGf0&<)QC6GbQ(6MN-_=;`^EKQUQ#;_Ji6GVJxNgHgmbKc9&M0I$60P>O z_O1N05s(wq-gFoy*}DCH&pYp$ZXY z?#xb<1qb~2PC!66Btu%%B43!iK#CvAzJfohM5OXtFkrtHBxSXhAcafW6pJGd(R~JtH`hw|s@uMn z#CN=1(r^4bwb(bQ>xWBxpJ9nioGhmG*b0>lMm+J$shyDzBP7Gthk1Zcd) zQ=l5`r2jYpK&g?R<{W%0dR+#laN?Iw64bmB!UH!n6g zcqqCjaho63-Uv*^Y$-<3jX!N!y}ApTHY4l+mJKXXQFsnx zFM}2gj9$Z`Hnc0>zryc7JY-&NMP{o{6$5!4IchI5b6e*J55d{ZB-PHYjauje@I7nD zE~Of;fNZB=MF8tF=tp7&z9Md3O&O~Lf7TS?qofdi3(!D=UtP5926Neb^Z_kM^KE^P zjZe2nphVD~)mrHZuL}%7vks9Z36)MtU6Ijb&7RJ0EuIc}Y&^_$L=VPNXPcCu zJ9AH9KamUpIl&l0uy6mh^#BOw={-7&%k3`W-pXW> z=l)@)h97R1(mQ@ol7d5u^nFC3VP!oOc{b4!24 zBcy-o$>o>OXM0n%#111STGupzNS0IdL)nt)PF7NhG!!##EcSVO?y#92UxIKw{c43ulFN$iR$*WN_8h_5k!5!C;^`NZV0 z!0D3Q3hpr&Q%^vy{Z3AgugdOR*x0ybQ?TszS8O|fG*JCJnKwj&-}Z^F)u`Hi;{%lWW`vKc5*dTrC(i5B~jr{y%FPMV+0CuYE2vrTS-^%e0}YMBq`Ej__}RVdBA!*=1>) z2$Ti>KopyuJb&$AE1q&TE>ORKjVYc|2t+@FbAU@P5)F69d3jL5?-3vo6S5Nact__b&kM{?P^AZkKfO4Q8`?>9L2}nlrmvExOrb8odfqX z7|R2WGO&&K6~$K!zzgqmijTCSPlXp}GOJzH4r;?-HY0*jyOd5<%`zI}=r+^}h)`|W zMxMS`{`75y6Tb-P`$Cn}0b0tlYd^e_O2z^l*u?56ak(#5|Ic}k<>O~($4+dgZ<3ZP zDL~R+x6Z5SgEBqzA6<}v>)ed2yd>k_{Bngm9j4B@xt1C!U*!@Rol~D3r8$JH5qs89 zp)4Omnd8ReUW`=>9ookgb3wtoWJi@jVLAZ;1s^^xt0-B%{4V8=13XrCyukMmTRb?e zxv_3*l(Lb$Nh`d#kI5GIPYM`tPme$tQ`wx~c%};lHrmnQzU#|RXbvdm!dKu6-Cf^i z6QwVk2v`|dPIQ-MbWp-_`{|^xKgerxPF|gF|KOWYC4pZmJQKp)2FE~@ziIE;{j*GyYr|o8uU=u9m#4oX{Lr!zq0urIvst5 z&ek&g`sm|4Fl)6-OLu2OE?CaC{QEY*lbb?eKCZnn-X|fblcLhwTPFB#4w<|I!JftQ z<^{i$Z(R4A$p-QU5Ve(#c?T~-Hx{|+kW1-B#K|brB%E7=kZn-4AOY?#1Rj>D4BWK# zg1z>%GJAP@wniIBCO5=s^AP%(ZJqeG1QWK645Fh0u3a%EBV5^Z~kB7q}hXD3B#PlE4J~ef9q}!Ee#r{ z{{g_jjd z4x{f2v~KOOyAEj8bFf*xE{<>ZBi+nYJIs&@x)UW)pwfe72U~V?lnH{VM0bv3&Y%tA zmdG$-?XgpnFp1!-Hat5=ln3jUibCoHo7|l+Kp=49Pw&@g-u~6s}-=J z?CK%RRKO-E4F1!~sxQL859B>DB6d(eQeF*2v&3v?1AMsu7AvjI+V>C1#81kc*w56L zM5yb!pEUbv{)x@>u=Pka@8FErFs)DZpWdK$iBuelZ@nyv4rrFg)B2@%gyS0N?gS)v zj+g$}LmQKW0fnP@M~r_ z?#$@C8uV>tB+X*;d6Bs89}_<<@)-c#S@LUgYM2}{hFFtzJHf>s$;as)M8>DE>~bwK ztfWGkPY8BA4<6}d9WZguX*fL|UBqdt-rNc5vv;NpW0S+YOC#`#{?ym_vk#DnBP2ZO zZ>}Y<%-z$)$2%v63(6b`f>sv8;ZrlA<$o^j_>SEdAJR~NKaK<%kzrkTf65qqe8RzYqr$Z=3ajq()CjfAv&<9)<(EUEHyVhAVW-W6;5>VA zOUpv?%==6P;j&67O~P{g8n9*xXcbHen;haKw>^In^oXN~=vW~Q;U@vvQ0MwbX`P;= zb9%#|go)$|ddEuxQ#8hSxk3=n-h#1%3*N4QraE__M$icH-6N%&SByW3&B^aSdtYMS zD&e<`&uD-{bh9hn!f^9zai?QrIQtZui?RzuH7buu+>BsE!q_|k4bJc>;**+zj`JJG z<@)BL9Ua}04!s?c1D{GC4MC6Qeu35QS^GZyu9=#hbT2*hWKxRDJ`0-17mF`53C~K8 zo85Us!;;D3Xb`R#?Ab1LsqK3BgeDogE~~a-SN+EaKlx<+R))SI<^T5dFH}8@Mf|<; zpo=%S+qti%mH2as=zV{CnCV~BRK+$|Xc_+057fp& z?R}L)_221akbg2Nu{+bmFh;qIQ!qQSQuDkvEi~vU=8g)FQoxpd?VyANV@tVzih;az z!Hr8QJsJL>0S<|E=h4$g2xu#OYA9FfouX;sMt=Sm4U;Tj`P??eSujvSK9J;z2sCh| zQO>sSnj0d0RQIEe@Ly&j#pG<@`d>^BuJdN7q+T z?yNan0xOPwmOIGI)}}j?@l;}k($%v01i!a_FLCo*UG@U_cJ95gs5z@bNp$@JOvhs9 zr9ajr)EFKdQ8DUf{M!|uq8+JZlK2(XwOSM~j%F@Yt!SiB?)2ppjoE;u_D9BtWXA@_ z+vNa2o=hd_@Z>MSyREW-6R`jYgmlA%(>*n$&D#v9N~niUt$3k4kFd7y8eHYxSlLH? z*^XHRj4a-pUvA#))cn?kz@!81%X!y`-5Z>_`kmdNJ`W4<50ap!h^W7}0ZqAqD#vQs z&X!(0Epl{*vaohqDAxs&Y~D0Er*^bh zmOq~e8P!#YkOapo=9JoabzgDb=fLXiI~Hroe-M1sJNLqBNx1+kLH;J#{-pXYwkb=f z`MXGqr9gLZoXnAmnMq1~#c5eS3zK2?t*N*+71=Jao0nyXC+hy^&^ zmm6|RbOHM-TWMiHSv0QIz3d9q8qUE*TXu|$*kO+D)Ut5e4AX@7I_}`hBIdhLW}lM9 z+=O+SnU7e6mY~Icf`c{)oGaq|dFWvbYA2BuI=`nA_3@gavO!Ifd4RXi{f%3N!|%5B z))_jq6XWdT#6zLbpoH*H6)3u_BVhc7anMpN6@-jJgK|ELSVb~(!Y`PZtzoG(Ap7;j z+ZPq2hx1HU+h9Lxur94tG$vtHktC0-C(bEmz0`L`A7NiXw%@zqNwN*3V0-1j5bNG` z9DZ3C6F+0^&=9)8GRD;@70w0c1|}tw=y@X*3IaHRW#Ct{(s=xlL{NYkpdIcb5dasV zLb?UXGCrFtwQ_BwYIEzjV?`2Pyb9C*CG)qD2>pyp7E9M0bB{8p46KXCB^zpdu8t)p z1XXj+HxS(JT__pLV$We=R*_g2n_C|wNha6q&C4PJacd9T@G)CQG3v8^;L!el{!M$G z0xW!kU2^Pu<{vm^$+U9@_^9K@6>bsQSvZ`+>PvVAlfce_Dsn5s)bNo$es7ymW^PNw3F$J5K z79aD=^Iw=l?1WY10v374ro2i;Sy~`AGy0hQi;^4^L$3fh&AE4U5;|?)+Mu?#bs@>t zs0v+tm${@esXGnCc3MR|C6^0bOKJbJgdj;Yszxui7e5CbyiPTQPd2UPM1{O8>nB_H zkn1=)fW_9@hH^cT9fKR*58XAudE-VlozWRqee2xRHPwvs{#nHv2UefbT6LwH z=vuW<)n%T)*<0vUC5NV2>4rf*$a4ZhYRzvB6Sf^mFL|wimcEmz3ZIuu5^Cq)u*3>K zO1C5&@TmQ&3>M8ai--6S25EpTim(dEt+aFt6Vjyio!+`HB*oe*Ig^YN32YSHU4i3d z>8-8=BSxr#hBxyK0zHRlA3YL0=_4Qzl}&d;SVI+$PMw_Ns6v)2|a>k0;J+;uF0I@UI=RL6t1f9*tsabU|OXquE z_TCGsY(0t9*+dOz=I}=3NrJsONP_tJO?l*N!U}c)cG<0$dn7DEp%VXL4lNm}C=Go- zxo+s5etaQ?YvmvQXZk&;7|)8nTWpMrl#vrtNWpc~_#Z^+R+FxV;D|< zO{F^taNG!-AICfsj5LaxPJIVS6PyXeF6I9zWv^7IY$Q7jc5zTb?on&F7z%KoUG8gt zvRVfxZe%7SXwUu|nqzAYKU4bJW5}c+7iC|4`Ao{eDc9?R-JkpnA`(H z0KVU5m}zN3>e#d6_^rWQr}>ItUI2Q# z$jto_?NxbV5e+Mdjo!-tH&Cfn_yfd#YHRO~mS6KT13IoWqQA#n@PIB*P#JT9LW%-z zT8);t+z=ani;GEm5H`0`S)5;U0)MRN36tbqft@_NG=QaNhkcp0pnHJm1CU~bt2G$wRFH~Ed;6l!|NrQhVp%UDu=+xv$|XEj zBN^ju09Eh#Jr0n$D?CG~!`W`EZSuMR*=IQA&+##?C_#(koJ*GK99G>S$6mQunQ}U= zl(UodRF$(m zKZ`mhKjSg7a9u`(rpoCgftp$e&hNeXUrD0-Y(nS^DV0bM$jioTOSR>Bnf)Vj?mu_( z)+6(&@A6g$K1bjdmQJu~$*D+d?Y;K5DM5QgXXkC4JPrMDvphzYrL-szFy|MrEGFaUZPs^EB)$VhZ5mM{8 z^#@pnu#LpPOu?RO#jJZ=Lmk0&TVI7@uOtX`ry2W&fD-VAyNBvK5ik~IyLox5zZ48u z=MjF31x?1+Ih^**0$j_~-=m9}NfPmD2U#jfrR}keQAoJsM4tVIHuMW#Q5vICiSs+d zXK1pYZK#54i-*Ihs_OPt6kuPd2@NJG&PehEqd5JoS!!cFjna4RVrtvP?&+&W`8URu zGNo=~t^l%bljdTQ7>mJx7R5Gr*2Z5Yu$9buc3mTBPMJs83%w6LhW?$hjhT_DJZ#aDe=1X=jz+{-#xATOCxz0TvRJsLWE>X z0k73to_gqWg>cP9MzdSTQAc-UTH1YxDLeJ&XyVh*U7G5pn&U)xk!F*#wJ+X%44qd{ z;_|@+t4M*FBZt>P=J2?;(<=^8@60y?XFFd^8{-4>N+QX#UiIgdUk?vr(zosO?;wuUVjK+bKcLj;Y~XwmY3pm^JcWW*aq)j9*V|HSZ(GEF8f*hy$f?LZGdg8)cE=c9I8ND|gb6$e_|ok~ zT+5&TAspy&E(#~f%4OMX4M7Mk?+PgJhHZwWEAJUbc!Fzn>NG`A=}u&q^O2Qs@Q2H*S8v5=B9bS787xQ1eVAkN`{`L(rMhWHxjyPimFx|K=%fbZ(Dc%6 z-cvB*2KA;gWv_6Bkn9*@?xl173?s?%0$C~RuZc}?JC#s-;Nb__f$e4PY-E3lZ&zAj zmPK;084WsKVr~(cY&P&r)^VpqA2S((Tau!_=av_GXiKB_vnH;!Ces2E_Fcwa4US7eRgtUw$K-HJ016 zz&My%5BHS%7pE4tHSQQmxF9w^LIFR>hH| z=!Vp}z7>VMWCGPhvKeUyrgGG@_jk&$nxAhA`UvrCB*;n;p?_D1G>+16wbzredcJ=? zN}lA^)f_{w@B$@Ut_vi|4oX_cist7tU&yS_zvuKb1t#mc9H^ihc5{jrt#Mcy^W-t0uy<6k_hpjtO=o1SC`v)_~?sJLG z_i3vFm2Z3y=dSKubLNx$U2!L1!ydfM@Ht~DIe)t}6QhiNYPTJK)vQqy*mJU{;X)&{ z=SiTBurNyB!V^QNL@H=6-ay=XH}uEOU_zK~MuO-h{igvAg!OQ<*F~$^r zb^Ttv8XhIcchK3cgeYWdNx-@9PAFV8&$e#B5xY(h7N*;U^>1H&0p3+M%k%?%d0~}# zdKbA_w9W$wDT@4?u%EfJ|I9(v^#jm@^$p0HtJNw1O~F1z}4hVB5|&6AVV z6F<1vx{{y{oB}~zwz*P_`@c^gfH!pxyIE%!=Ci^at4W5wc2L?cv76_}#wsFS_$;1R zO;UgSzALf)M!7TMtI2_Vl$RyX*TM$vLu`%$oES#!1A?nR^C{yc>0B6%e1K2kc4`Ki z+@;{>pVLdmJ&vvRVvB+67r5jND9mc~GK)3WAcU${b^VK}te^$j{c|hn!mzAs`I)>1 zb1bjCC!6)$2Y;ng{RcyZK$*otL$M-r zS|Jm2UFr{HHeFT|s|QJo4dBFX6B9Mv7$_TL7~o1RB>?V|b<@D@rV;}6TjFXTP4Y-H z@-m}ASL!7ygHKJ3nmI)GYJ=*Et{&nVy}#AR_&T=!=>m*^RV9_XLQ&( zI_SeewmKaP0E?*2%I~rs1X3hK+pyWG5WMp8S;U9k}=SijzcyN_a^G;Z}ZUY(w7l+W+qdz4IOKUhY{PU(;(GO zuJt-Xy()nSiZ1e&#wa~-CQS9crKHKoHSS3QP*Y_*$HZiE4AzPcV!(DX z-$HkI(!7kdWz!#bTxxLz53U%3%X)WJq8Up*7dGrJ_hhx$EGP4~E?L}AHW)-RdBY?L z8NI&QzVUOjRZy;^@cX<+A;`dt&S@sg+!m5uVaOwPUKh#h%Bm{p7s{VvBNL49cbXU9 z{$seqNf`eNu&hYrr!P(37x8W-PHZ^{6106YdQ2eTRQ8f_(FN9HKVzFE%E)Y3&Lii! zGTBJ7PJBM4TW6b@XK?uJqC7+RI4@|z^C-QeN!M7tS$y=j%vt*$p|HIn=$@eR^Np}I zE;Ox~$@*X@Mn}#a2aaXPo3jyHp*K6R$|)y6w)J@yNFTffA8g0~oyg(_YlThmN^=)k zV|+_`C(j)|fA&7{T!vZ}&QrBwFV=pSOdbzw*-UBi`s78`dGl-4%z5&dsf90JLze$8 zz}6U32`#=K6YSZYj;Xml@AS}$j-B^(8sjr)MNm`w7s`1H&f;LQQ{UMcP2LZH1RUSs zGg@Q-T*R}OZ+fMzeV}dT(SQ{i#ii zUK+jWw)gkH#>+o8%g|=t&uxmWbT`_cNclDZtwavTO@rmTuHv$`HLB4&vOKn~V7%q! zX}+qWXk}7F@vd}ew3{tED|yo8lNpajs@fQ$VdVT+-vud)L;j>AwYfQI zYrDMR7F;bF*^D#rtZhvDC*}ayouVY&|FCxUeeU&Z-7ms^@e&G`WLH*x&u%43PupdI zOa@|V_pY3R_qU}UKG;DA;K0F{xE!^7L4m@MvmmQp=a{7-oXD@^nbu<$PwYQ!VBMq+ zp2SyRUT&xQ_O>h8Zd2mKN^=Xk;3h2R%UZAK`O}j$AKC^Fp!@W2dJVhwn`xo&B>McP zEW75nU^2smjwsLuNzqFEtIup$HJDof2D?4+{rIz^u>sz0JkQR)fF$(_s-#d{@PL1Q4I6z$5 zN*r_Kij2%^mxWj5ZLzf2_q0ej5wtL@SE$({B27a&EB+D3KH!0v67vogw+biEL3tpr-h26Vc{sf=iYu;2JQSJ~6AWyv>6QAfgiky6 zsLm70*-a8l!VGZyw?kh<1Ul=7xYvtE2=ORByxUjwW?VMYQhnA?l(59t$=9ROt14&s zfAFXrzGAJiLi0)|V@|dy^5qPqux+9SN4r?NwULMqh387@+zJ5<8vs=Jts( zj1_Q~<8+#%`IwTgBk;nOJ%;M>9krc4H?DV75>kDu+cqUO5wO{m=}133h9g@E(yM*J zT>7z(ijdcDrN7qDAhPI@#S7;!$pW7Ic?$swX8INoXwJ zd9o9romiD<)d|YiK3=|%tDU63I-w1UJY47AlMuap5g*JsD+vj_4HImOsKoTir`IHx zB(#aqX(oE71enYPImOV@&w-|Tu9_Du=y4Co2e9y4C*zapHbvu;EoE|qtOoUv9_4KD ze?w{t#;cW`VFouLs1zI9j!uPAEtyFO9&T1#gdSzI6;fP zm_4_~msbQWl^0^o(sT%CsvGMb9L8P+T{OmTTs5*lv7WB%`pP&M>%qS7pH=_webv-F z#S!yX%6S*D?0F?-^Ws_JA4iPm$Gkaxdq&>7FSAYCVt>mkHSfx%n_e^ce+!%bayG!; zFwM`(v)TQaOX;{NCH<94Cyqpeoj%^6%Fu4)`WNP4tjN-bZs&%he>Nhym*e!MU1 zN41zRK>cj!3!Ne8>W5s$!erDGerOUz75oe3oL#X^`5Jq#s1*0QJd`8_1h!ZBH-=a~ z({3bxk#8v&U(4?_1Tp^Q3&70(V`A~We#9sxtb^G`rUb@7SUfkhQpw0vu!d4% z(h+rB0Zd@WJWg-ij8Ci=3`8cfRcq6oYNoV6&@4C}@kT6;u82LrHNVP6_(aMErZ$4R z<1s-eUr|(PG0^^H-hYka+;Kz3<@>xf%<>YrCTZSSI}}pPk`)c$B^0|)dP?D25Q&JE z-19QTcfB-IC1l9Sc8J7`W9gqewg|OUuUhPaw6LEBA&$QNr3DrK_~eL%^_g?L#kdPV z^ovz#1Xk{h(&l&G*UtG>RP^HLhK#c~&af`N=owUcA~`R z0(u}MHZ62(Zc5arHu5E8x6}`~9$VTwF0{r!|6_MY!j-IXhAZ|XA&q}Lz|fuhMuiTdXwMrDyM-7@7o5h8Az7*icSmtaJvYX z)rt5id%hDpvq!|jmis0LY|H;WJS3I+tH({4-0|bDrAjo9H>p19Y=3zSnh@kE<4D&8 zsP-9>_mgJ#>nsrIfqTl9X8xf`_g&O{Ciu5Pz%)S)PW?Ahp9|Y?+&|{b`1JsdF2PK% zcuMLTI)%J`O@9d02gowITBy*X{DeYioN7&MbS zrtyKhHyUc}8#P~s?8F?;ObDUO9kB;b__XaSp#^x1%(+UU&}#F$-TK_xji-7l>qL*~ zi7j-NLX-u88jEgpl)n<-CT62@hPx4@nqq_1p^_1LULO!GD2SrG_pLwZfkrQ}UT43hnyP!(z8Y1_0(N~@E);$ao z#)^&H*R`gDG>o^>gXdLlxayqPb+ogT_~KL*OcMXKeSmoY4ptJTa#;?JDJ=DMGk#8@ zM3uYT$a736t$;sn*ktclqUJd=?)7-sVkKK6q^YjuJu|UWtKj&DmE-0w zeoc!KJ_!zn23PO=syj5YA{a5(B?g`~jTf4eryeye;&&dw!wWsCL)3P9ZLuregNE?@ zbr8TJD5Ve3piSg`-5*-;1?F!wO0Ui}lYWkO(u22tRg&$qD{cO21yAKolKMIs+QsWt zZ&7E99p)i7U{bHH|39$lL#WtToXW|wDN-YfH@^B5qh$F*4RmM!6i8qY*YUlr`Sg!-Dsx`gs&3-l z5dopcmI$|0^4F$LgyaNU{RqdBkwa>>)b;LMLtmVq#-jLSUare5p>XYU5Ok;ENE}-c z!>0~-Z{GZtJM?R3_p-ZZnqPy_xbbY`Vm%pUOkdQKc2n|@x%45KL)$L@qDczDSUqVX zdTv4vtcshl(A{hLSa>}hx-GdKd<1wJ#E!*J6ng;(Y>XFU;q{% z#_0dXiawkfQC=flOuJ#g=-+Ai=(*}l%R?E7uX~C2Uez$fQmd1n_Z`IRi!AfP#BX#A z#m@Ne{1D+pu2givzBp?3yzw!vxv*ezl+tJw`PA_2I%aMG&B;oQ0X?{RZB zR`ZoD#&R1iduZyWvJt+4 z`^Upwj^R_XuAjeSv%kFCBjh!YN9^_UTN1AOJwK@V#)_n1%7n-l_~s0-Ff+qo68VLp z-pK^iY!OPSf!$uziRfu4C#`$^iRul5>?cT?sC1^NRv-K(eN?Bh9N1REO%F|IzY`wI zbQzp#OU$L%BO;m$C)JcX|5N8)=@_AZidIaR>t)hI2eS=`)DN7>w(O;9sB_xom0oO= z$+(qf{7QNN#1RrVnp~^pY-JMUbb44V2ubS<8dbR{xOYKD#S~V~gmb=&`^S&t)_yn* zg@~iappsW{_uK$3fb}b~)q5KKFw)N?mauTdtIe|>S@m5geCH_2LM}$l#N`QjC&-PD z;BO1|A9L&cD_Py|Ot3tlG>lv5fB2Je8|G6e)nTrjetM!Wt0V3rqChPk57DVxHyk7s zzlVle<+yrm1oDaOu{zi)H_7?*$sY!LXkC+TP=GZDcjwdqp-63m^llu8AAR61(QvsKGk?@pG7s|4q{D5PcM>8)+_tM-RF2BBVCY>bE_U*j$wvdm$jbjuFIT39AUtwPv6<4#g3lrQm zcz~e6A-Dz!gy6wLaEIU)2(W{@6NUuOAiEq!W`=W3 zv#h1q+H7YRk4dVaBz4wNmQIyGM&m4!Pn_`C>1RJnyTlBVIzMnz4h3bOh)`kWhDn7D zqBg=zoyp9lYR0>nBeVw`DwtQhvde&5P|*Dz5;#3>^V6D;Lr!R!BM*Y#A>nO)=CZZ9 zYlz?bV>2iBU%Qq|)`G5jOXMcRPP#Vb+V5wgw5pMhZtlU1ouRPcJIrhN@&3{B-TwYy zb@vEa``z(e)&%3#fm{dtYFSP;=yo4^g19gb{MOYOfG9!ULr-r1T-@!==iMKa5O)Ov z@X-66nVCVkcHrU`3jV#XrrYNS>+nay`Q6rn5O;g#!Ph9}nq1KB0ZhIN#ge-}ni-Kq zG)V(D$2WWPCuYmH@R6Cjyw0j$2UoEB>XWW1gRp-xIe$b*?PpW(NuXt$R(0&u?@c+5yiiVLY5#}j@`*2@3XoP zB?yET<60-;H+a7Ke%b9Bc~!z6_xo3GSMa<0-aAIv{pqhpq9LH1SS}bmFw!x z1A=d_Uvq5igP{|W$&8)wjk#*zdbKO)>K2yQI9|Ob4V<-2Fv8jcuFg-E^soAHkQe4% zsMtiA=OQ;?w06krExF*{@)CQOsW_vX65#sxNYOQr`+Va5c2G>R8-~1CC1zX;UOhv? zcRJQOZ*<{s#6gl)UT&^j;B73Tgld$d<01)|T%7@eBszX5SRsbVgYN0FySkC~Ek4x~ z2g~h&*9L*Ez_ODBj1P4i6R*JD2oiDeD>xVdujoC$$wNL!`{PdmHo^iCLHp;?voY|# z7*mtCW1^uUxWUdV=rq9R5!WqIipVR>TcSube(RXl5o-H&R^Jq>*A$r)rZZ#=_)~;r zwj3k=d0*OO4b2N!eQJ3n5{F*Yh!s3f$-B-|obLJ=cymjO1>t@9R4&nuER%B@=eg_Q z&D5Ft?U|#$wHkz=cPLDrzTne&q|pAs(S%&kq6S3zO+mUaJ*hH~uHhFWl_pBfp=&(8 zLWpN6iW?-BQ2#E0!wLv2Lg0x}#rI zeieBd?%JvCV%VE0{tj@_ecv#nL$F>!-hL_?z0-`Mz6j^s-t=I2A18$mnYE& ziFESMXNjZx0StUADCz_&`5(-&uOV(5@^a9zw^8&^Erq}O5-;o98e#LA zmq5Ur$3-J`KSzML21)3m^ZPs_zl?B>oXNmcxYv>_8w&JAGT-PN$C%Rl`bZKeJU&$CYm9P&;3+cJYQJ( zMoe?W3ZPLDhvhmO{>|8GnUY+N!nDVk1qyN7k?Z1C#Mk4U@A=) ziF>IDUi7zk*DkGlHw~$qW)e3&$>k+(3C2f zXH#*CS5G^WYqlth{t7Do8gtFP)`*v2XMCELK0npC<#Q`{R-3 zq0Ql&Ff&c)7rd5_Z7rVmD>)+Iy_I1PWG@`2D^4PXZ__20dTe- zHwdghES4(;joTSDDs)CFR(@EeF{nvi+#u&!4~d-P*#??L@E5*V+Tt5OCS3$+IvVXL z@TqFIeDJ3=XixSaqvBsLiJar#E|M)W@3y*N0BOE2!d)!K))%92H;ZI%nkOwMAG8Wh>n|N7IN=1(E9=iI&+bZs@i zECKUK{wZii!}{Ft1t;a_ktfQekqfXd$5_XQ5oi+2jbWYYvSDkV@Satca7k}bn>e($F#~^2?|Aqb9$YerKz!6euFFre zm6ae(JAFro&NHQtM)k2pl)L9#-OIJRltiO&NEl=4eixZ~M>Yb2E>*2hu!@oRAlC2% zVxt%KHJ3K$L*3aLI|Z7lnD?sC__Ok7`+j)CYo1Hm&joq0#ryvZseTQ{8fG`ZcS2Xx zrLP=1Rb?N!lu~bPURGare|y@A8Ozd^rtRBoBp+9oxd;8%{0=w-d$W;|p`raMMnfY; zLqqd(^R(gj^7emg>&5rZ?KPGP{;=~4^AvK;eq;R28XW4^CLISIP zyCOpnT+h~$@zEqC|M_P{5Qak>Q&cghC+E_3y7d%4`SJzyCw%MJoiS=ejDFGa6?IL9 zVke{h+DV7lnH>d+GZ~b3cB&x>iEt<~#0)UEzAv z;>~5eP13_NXXdOiRnnv_Rz5!Hos<-WmoTkS-ZD*nJ9Ctt zP-}=2b)e#j+oi#G%so?{Ad3)V@n{X-Cq$p*A$pooulk49Exsxxd~>sd;l??m0?pS0 zEeK5jZ=algpmShhHzO<7z&F!U?twVc0b_B z^>FJ_;Sa@rtK97M=1mDJf8uE-y{imjH|+#@(E@O0Ju31?%vln}LHl#aQghGFl5h>h zHL7K#E3E+W4qs0#iiE9bmbAmA{S=awANu-00@f&427`Vrd2ubc%s3$EByLnmzOSW z2p@p6_;{xa`#sC|XQ?V;x#8Y?RuAQ}Qfay_WSEnq%c=hS+I8k^sL%|48H2MT z@5Z2Dz=%0j9br@N{ zeJ1aIg^u^QjaWObwy9a|X;uF_sV!Hc1J~v(NHg8>ZvIhZn}qU~$J|fO=CKZLUP-C> z@Y=_=n1pz@g;KPt5*g~x5bH6WiFT`Sm4+JbNIcuty`z<~s{fZq4zH zpkU0L3{A4Ysuw!$j0`2J@Dx4lsX4$k?T{;6G$s8+9pXQoRi|YhFyoMWUOXkeuL1Fy zUp9WjD%6(+Ni@5QX-W%^=?@*h5KoxU{f3RU+D0eSL$ks`BQY@)NE^Ms9AH#Gc5&rc z9%cQ|6w6*VpqVYT`cH>&CV5quxT&|H8|E0eocE8k#4_1LS{v`5R;d+JSqI=@Z==ab zdvO+wXW)4yK2&T~H~5YSFx2f zt!;AwdZ({t2=@yzta7Hbs5^ZmN@%pEAI;%o@#Y+`tC9K2L-5{+CGiUt6O5f-b#6oS zKe~?Y>ZT5&R;8acoLr1BVD|u~LMJQvcepXSa)qnSqAVU{*L3PDl5Ouf#I3zjJn;Ij z(WUL4_t>ooTzIeZuR*rVn=qW!8Y+%SClY7Wv|Qv)h22h#G#*8&lAQpisr^>BL*U*V z=I$k~sZ8flsx4xN1sTpY>k#mMA(Sd2yCI#$>d6st)t$o^&^+pguPr+aF*SC?36`b`*vGNu z4q~ZE!;UT-gvcg(rE`J4JN<_$YeUHvotY>&pdg-XVTSv&Giea*p!3sTFNjKrKP`yb zqVsUn`Rc^caR}|q)}Cw>oTDmW?#td2xVX4p`9YICkmRsBPLL^RF&llq8ls9XnN=Te z1I}e~V?ED)m#L!faP?G_bRwok&t(RcL@KT$PYQFv1oq;#uYN9=%wc3q1o&r)v#EZ| zEHQ4@k78l#F@PGx)PN+irj4%x5y1)ylD4W!Pvv;GpIq!`hX?xxWNXE(d$+!k6}S*t zTI4h?zND-j6RWVcpLHW?Q-iz{O&F@qRS7miT%{wiWH*>40QR+$=1IO zRtKc;cP&-dMHz#NBnaX8`rndGrz*En7{2&e)B%bOiQ2x=1_i@hiLu7M=|6f#h^8cC z7nvtF2z+Up-LZB&HVB5>!k z?QswV3M!QdP6w3;lK%#$3g)I}BA(7#l1fict`nwOPyDOc%Z0&eyU0 zN(-`Mp#NdgCK2mfEl+VgXH)Vv7#p zDuG&K8)S*JMo$rgqYOP>YK~<1lM~L4vIs%EQ5iER!|7`Mt%~~>+U{mu!h-D5NNrcW z-YNn$5vA@|geWb)!{kbVV?9Mq`!eQqx;c0Es3O|O7NwCmDCJP{IkbTslreRYa8Ce2 zYxCXVZ;R0%P#kd`LW>oYxp6(kuk2+zJTD`hD0(BsT}kY4HYIHoNvN_3<)hwyPxc+n zAix-Wi|eWHnK+W6K8+g1Vm(Vk(NyAx%^AEfjvqu3b_+x(fB8(xAWN${N2s1X7E?bu zy=j_#fogOrqy_UXmJR^6YCd{fbw!e8t6}vFJu&|+|G#37#a9e-3{p1Ss8}?=EloiY zZAwr!=5!K9GJbs>_!p6l`~L|4?xjxmKZU4H{vQ{B)t$$+QG0Hh_N?3>fWNfu`KhCP zs~p3Pz?j2$>Yy+lT=asd?Bg=kM>kvCw>M*Q!9hJf>}%bw(ybJwVv|nK{5M<@m3+6a zs+4#^hy;GbCvIco4n1yQVlHZsZ!_97TBYY{M*8C=WjUOYP(kkE*2LEpm-V)%s!P3< z#@ThiVaB!4r>`bAj)G^~gr@A{BU+hrFC+aeDHsQJDt=u=+bPV|*)6%B5rZ*5gW=%vly=pm^A6^=o-={)p$;UUBN4Q8UJN4RqJuB6zgN=X8*mA4zO#IZ= z?ft>>a_>sK>c@i|FFHtzQ%!(TrgeP(!Ti^EK%qvbONu()=a?Yu(--aCu~P8dz@Sj^ zcQqt1C`(*uX`9eRYPnQW`7YX5#`31TuWeb`>G<2+uMF6@U+I0~eiFp}Wa%-z>i&UH zK@zqw*~ubgj7L9b5#IxmqM9xHs*mu!&^stgRXn##U`F8Ndi`eMoIQ6ccOGRwcV6Y| z5nxydOAXu1IFie&F+OBY=sb~FWrX^z=R9K#b2^^O>3aFLjl+%-HkOL!u#6!8$20^U z2R0VRrdL{}M9PorUf=P~M<5frekUeIcmISVX4ke&_#SQ2B~EXCB3#5$gp%ek{7)X9)id3#ZED` zJO`(qf1zt;RwuWRLjLTVjfh!p>J3dAe(P%tn{0)~#sUvYiuWQNXUeYHlBP4{aIHmY&?y70B?ZJh5#Lz5FvgWU_gPp$%9ALS<`AD2B% zzbrj8+ApvpvJ&w&_1u;z-w?%8;rbQVRXsd#XlejEtc!H|+}k+P>f?Jw{AJQcT&o<-WyERdjk z9KjY^)=bmkZ4!@BV+YdzD3RoY+D2f`Zv2amUt>Iu+CkF)ECA-&cTzHPuC3xLsREBM#@FYPdVBs^^bE>CO#a!3J3v9Y?6&zSUF=7nFQ$k6%4XV0h z(LH5)ZttUyVXWiXgW`5HT6%gMoO@3S^+c?b+a$aPm_C_~P-oOrYu0JB4BPE{-uJ4C z@4{8i4aENVt@h37jlM#-*x7N-cypf>xT`n5;i+zX#Bc`RK`g1*;;T*NtWK>fVVUlm zc=Dr9#FdMfRP0xM#MTF9N%%ZnWlA_$BKr+tGRRZwip!WsGAt`lWn?hB z&TFwpeCxjlkb4mR$%OgbQ@Oe8$zA`2fg}^LPS^I!8Kc3dn;2karYRuAtFnpo?8y>{ z`}0`Br_r1+`4b24!m8=81#dx<`{s^zz40zL{t4Gy14n0%2hJ6Y7A~3-$G))_)k25C z$@S-?9T>MHJ124E8nI}Vu1~q+N{!8iBAS+l_q{YO&P*8^&kfBPg2vlgxy)9yQ~u}I^Mou?DBwk~Vy;x@ zv)Wpi%6aZ~NSL(4=lN8J$#din7eI2-vk8^#EG3~Wd2KfAevG!TIg`}ob2xv&)aPNl z!^ZDq0UFM%4Nq;5{gTd+HS1kO+Kdgp%?-M_Dgo|(-q-_O@9+CnCS(zZ`z1gP4}8PR zVI|$p^kqjE%e`a6z9%)QCvRCX?Nb7{I~fQ3Fd^l754=?16DZiXBx@z@)(B_Z+l68w zM!uo1pq+L~lcp=q^5MrAcD3Zsy++oPb_hDw{Yl zcCppUFKYczUCc`?-Z=jJY9@ev8|fPyu@p69P;_~bTf{wmueJ31JB9usa0J?mNu7-(;5T5w14duQ@ z=RgPCzA;fmo`4VKgjb;2@Jvx&_T|kv^^8&5kz*YCM&kmU(Iud($!TtsX9H`zH|>i4 zh5dVd45=DEexJzA=zO#WPTQ(^ngH|hUR=EFz1OE-W|IqO=|9)rwdrLlr9U)taqPaO ze>0icpcejpa^QWM#bF;7g`bY%&*EmT%FRw-aY;n0KdSH)7$A=t}-l!rR}6Y zdz(9z&LX2eQrzg#MXy-Dfv$>dmzPSetzhF->*M^N1*<)ilSImcYBc`9&K@?_j4bb2 zs-_b~G0f%_4*C*1CCigPl@RtR3na+R1$*^z#;7oV3jgD->Y%_#rqH>hXLvQC;4}KW zIwet2h;ZLQwOF-{FM$L7fXp1Ayu|-2zL*#}PHSuy5dZu6e1 zAbqXpk^i9qeLO=(m4|-0j?fC(Ne*JxcN=rum6|Q=<>-%to_Bp2{yt?M=#-dyE-ozoj)m;B z&11SEUF|^)O~e43C9Moo_4s&nrpY@J24yqK?qa-LQ*Dg&WiJa|!aFkzX7Zw;k=8P9 z3w(^KcCpv7O1NjR*H~1hS#@{B%Zp#p-uh!We0(rG59H6J%ruTXbZulKJ{WtrpqJXk z)XEgd=x;g?8EJ_MqrkeNz;KI*!8xS+&_UN;!}@(Umco1!;;`F(E)wr(|NpK}{V zm8zb8aOB(ZCZMM$L|d<+|E(3)dStMcz!ssfvy`5>{XN?NbzZUG@W@zM{s{||KVCGC zkp%ewE$!5w9BPL^Kg7agWxt9zeT#+`QVD*80n!3VdV?!5Kzx`~#&h2q!Rr_xO3;`u z_zDBWiuvTswzv_@go$dZ^9PGzf>#sTpJxU5ccRwL&CMGef(fDm|G)&%f((%1IUqJ^mEum^8*dVS41Z#iGXut;8AQJk2 zR{zk@Q2FBjc+_FwP;3w@=YOY?|DlNQp#Mi=8BBr$q6DKR2@-;TD1KL9BsS<7X!Hgw zj{{<*|9kd8M-3B%_FqHz1_y`WfLQ4N{t8fxc@*P+hes6&?#2O0KfnZmz(^dB>;p#( b5cnA`NcDjm*53t6Z~-oe7Rv_*rHb}H6p}bG delta 122957 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%0DKC!H8^HMC&m|!hhd; zIwjPc;BMntq3H!T9NByvAQ}KR2#pp*20?6c)Metut7QBsN$n1>HSgrKa)_86@TfO zzGbum006`b000pH004Jya%3-MZe(t6bY*iccx`N~mdSFXFc60CQ#J2Ead*Vxm5QB1 zJf6yRmIqKIVW^;`Bw_5gABlZ}Ck14D5ORzD^wX_wczn9j%()P$QqK0S|KPa}VTj|D zo%inVU#G>^b(Dq-!;CYscW*?wPk)E6UmlByFwD3n$}tG0qC%j(n`kW}02E3Hh3bK( zNOHwv{eUlrlTmA6RSiJPSst<(On4 zZYX^3=8{FV|DyG`(xb}1zO)`B8@H|5ZU@L!XG%A|a!l+h_ajF+B}|u51CnGWj-06^ z6>WE*b7^KVX&{%60&t;ObAMkU{E?;huhw>~($E3N$ggupvr75LMBf{cO^awf#z^>k z&qfI~C%D^qR%m*`^+z_J1`rGYYeYuAEE>kPW&qSxx8n~2fi26 z1K@lTA_yBD_F~<8EQK{rrs0|#0qYAjOrUCurXt288$m-{_K4L1%YQg(`8a!IWa#IC`>1(D0(?FF5zot6q)jM zOa(e+2JC1Kvb~g3axLxl{M3^;vlE;g2oF?GdmhHa2fE8lL9Y(HEM*6RW;C{69}xTk%rw!e>vQ!8>v?(jk}Thb<(68sg_PjJ;Avk3?@S^6RhE< zT%!bk%TF6|24I18yozb855ROr1qR3LQwNJct0W}-pfzEP5*`rR4+;vO+8fEdT>q@p zH>P`;e*P#m2yM14YCXRNV?KsGAbb3 zryQ_NXt8c5+=iw0SZkYSVW@yj%+F(&(9H6Gr-`o9AeshIYBVw7&piuE63F0c<4&$Q za<1F3`4oU&4KVUa!{{#pIp1=}cL+$hi00F6~qbuTE{G177?%@5$?JkG%ITR1Z* zsj9oW|G&B#HVlk;pfV#PzKo2Fh>WEE@W*rD*9wrNUKBppsZV6R20}BkywG{Bua@$Y zP_Lz#9$LB|h2Xh<0crh@pa1^v{!q5;EU7{1Fm08w`CNCiEN&4*YPulM(@%kCCQ%yM z*?%W93W&(IJrfXRlvo5!QY3E0No0aFg;RC(u+Y=`4bD8j56w#S5(*?{$-wyq zgZg0*mJkWyEfDX~Mo{X3 z5HzhHM&OAcvVh;T?kLf7Jx{{cP5$X2k5q3}^0%Ld1n}QCEu2myz&Z2N>@J(U$99!? zH!|}8gc-IPkpLgsF$z=Hiyzq`_$V42bnhU=uNj~S{5xM6Q|}D5@1?eGwGA(T?|)JM zws(PFY5wbo6#0(bL=X>&_o(pYoOewE=)o@&f52MHvpUo-g#KuF@zfi zVZKB+!tvf2R(G6gR}hZ(Vp!^(Iq+*`UIAG74?)n)yB_EViNC^1t;}+N4DP;11d`P= z-PO}a&{T-_I}^bjk$Ht!IhY@rkAHlIFu{)*jeP)Kf>(POJ3kkw{Uplc7c}qZ(1tH5 zyF_)<4`AH_{&f;ce@>Z&tH&^t2WD&Ngi)d!KGXWiy?t&#d@L-VeD|UEHj;UvyQ3o=*$q6zdexJf;FZ!e}Ar%WS4A; zLhnK90$b1h42@Jzg+5F0&mz0{P`y^6L%-9Tbplbn5USd;y(G;hUI;qyu!bh0n^5*O z{khIi42eXD2VnK8+F>+HqR7UOX5>f7eIB+gw!19C%AmPH2aUnmO%om_)C~_~K$A*- z29i+EAXk{daZK#E5!{g49Dkg0F9A?DxQBRIx6BTkaZ3+P7tU#UX|{x`uj5wx(VD<| zoMP~rP|KLAdZpBgRp`(28P*+YOc$CMw11e$N_nh!EvM@-XeeVR^d`N$Rwf-rrHRgnV z3b|_N%E31|hHpZ(O{llX(t<$UwFkBh%&b@O`gm=~U!m=xxjBx9E#uy*B+5jOr|`y9 z^{mL8B{g)!Ff3iKg@1Ygee*y6{O^DL%fHnSrQ$NDMIVZ}o+&4gW0pX&-f}9^z5e?^ z`G61YS5RG;*K2w5lNqi`DoAmdq0+c&z$GkVtT^hWIOg=$*-irV3lYEa3>|I2uUemY z5szV5_dyiYP~*9tz%9Yc(u+x&RjhXwL=Gd_^L;#tAJ$;eLw~UX&ti!*@;wWU;tpCp z==ezu$G+jvn8(ipC9*0Si_=my#e==-y&w^29_(ZHmE#u5UzHAXU_&2(Ypo{uYTkQx z!9%WYf!~@M10rr4PXEBZ0el;~g1PZy>tn}$2&2^d8^R9>JTpvr58=RrXUn$${Fj+4 z{=1PX)1amiS$`K8AxTt)RT`TzPU3o+&ENrpi7+{3C_~vWQ$5$CTdQ7!8S45iI^u^e z4o6|qN>Ex$Pr07JgU}y_Fm;L)!=OOM9fsv;6wj|mjMpP!9>8M&r$(q9ZX*87zc+1| zgElZiBs44RSGn`KZf427)5(&GHA+k%Nw{5I7AN5XeSbr#HZP2%Jd11(`}B)-ZXFzJ z#23N*x{vG$6Ljib!5sMCO}FUeHnWXqW$tGY`4|F!O}zE*IA6kkH}h5q)k*XJ6)@W^CkS$={epUm*>6_9Eo`@INN zWrk-$m4AvBN+vi#|LKeZ?oTO2((u|I!ASl#HLF|!xiDP+?M!Lu222Qf27d8@owdj~ z`^CkpZt*PnsR_F!GH#3I#ZMya1!?qjmwkc;@UAdGbiY-dT;Cghx*NZR?EWPea(d7F z4#a)%D}d`0EE2%E^(Y*EDV|UP_f+jlEATAKuYcyj>VISYYL(v@s03n=AbncUBVMz= zCc~(JX$h4lHZp4x;Zh|-0Rn>xc?-;&6i1T3$8WR=j(tW~l;`@Ymv-AyB;jXhsm!rz zzIuA-wI7VG6RJItN^OPu9jP-|<|9d-SyytZA=2O6Ohx(I2tKhFEbkbhp1ZFM5LtG>airp8ct+BeVQnQZK{qf%_B zea}q{lFYi)w#NfUwJSie**HyY?z(JF&B<1ubd3Yi-b{0YSkg;1nvZN!EYDlJ*B`86 zN8N3-?RsNL-f3wAYfD<4p4d%B)6slUV!+8vB5dN=#hcT{#vR@^C!B8kTL*Tf*?$mi zjQZFddUY|#d9k~&jIp_7YlX^t*Pey}3vYJr#ztX{6jHawQE-V*>*0K*k2f8)n*l@A z7X;t23?-Qi2ZO_K>zL<$QPM^hN5a@`6NVGoeWf5|0Rt)xb#mdU&Awv^$Em)u2F@kw zX*R z#QFXut!y|IPrJ={z(s4Xw+Q5U5=$}>(AI)tig7a;c1H8jY7;2BFqFc;qJM(Xxdc1o zN|gt&6=X3UCNm`)DfF?jq1cvB=f&`_xe$btjTbP=)4{62P^-;$(ckqpS~ZkyCMZHt9MMjXNVM#lPibBi#YEbS z#+EPgq0J7RMtP0P#w;Ah;(w?OH(6A#f~`TD&vsW29O%tuhXFT>1~?p2oAEPuy7D`V zB|Y{N|CqC>)|-#>Hfye~mYbZ`S{`=C$GIlX_L(x04sCMBkhwE%c8;c1bd7T5M*F0GuM<5@txEEbtzZ=^+dFtUwX5^~fLBV7iHDFAo4 z+6BpBvft|)eVzd(Se%PffdL6Io)Lkw-gNw!1ggU-`39`|BM{iyT(!Hx(wWZJM*rA2 z4!uKzPKH9ZKjp<_HwmqfAau;! zPm9z&%H?Fr?SNn|foyy1($F`#ahmai<&oLXFWqf)z8X_H>4Pft_8d5+EBhUZwvS%h z=Eye&O7S-KJ7Wwe_jdw`=p(HS-;>uulMwx?{!x)n)J1o7% zf$SIYAnoov==#;Bkh=?QC$vy<-Mzh_qbBF_=NUm1oxPl_yahK;cT<~+x)LuOdIPD^ z*fOFr8IO-N;4^LRC{4NRDTk8atK@Lu=*(01yYpZ@mXFGHve~S;rhMIdsU>S=CQ!Yw zf6j)bWhIkBcYg*+)S&HYaXywtd^oXEv%F{xXOh^9Dme^@-FO)Vk?zxncpT`vBp>*z zK^AOOK6Pkjp0o2rG1GQZPTJ8x_rtC2%*8EF@Uh4*=U4uaXX}Q`T&rC^P*#z<7ebQX zo@jP%_`{hADP0#GW<7M6$ywK+S#rN&$t^W!&HnnjU4P(2yT*XWm8?R8YjfKgHkH9e z_SR}nN<-*~ovfEhr}o*|_k@K<&r`5EM+VbaW^UFyt`E9GG>8#jm;+_sUbYWaEUTyF zIQAMxZ8}jmYIJHCyuH`Ng(+zlrF-ZsTyDwUjhVF=&*#H9v-NSR#6jc@PbM@2I%pEEB* zqi7&%p>Kn=0)35nuA`58^_rf>ur^81A8<%RbQi6zN!8a>d~ZTmwEMlR>b?CMiaunO ze*NL~$3C`}Eng3vJNwmr>P%mMgN1VFP6+R0x{2;$2}nUw!24(ov3y5^M27mM5K~W0 z&s(C2p`r9clnkUW^^i6|Pctd?^w)#E8x4)Si1C|}W{OvGBey-vtCsPj%TBM8*p47r z_AT_+1^UaPYAr7@RLua67v4GlcQryeBU!>M@OdWndo}p{^FP*<-n`$F)wv=q5$g-j z{~eR10R0b@f$v%EZz1q$WCEM*}@0RS#? zvk{eA1%KzS%JXr-dfrnPz2QCe)-v6;8Dk`6z0ZgmK%&X=a?6wxV|8gu5uW4y4qo;>FNjh zmc))sqsjQmV@poE%7`zK=^(|DnXa=$1&=x#H-DH$vnz-F$)OymH>VtU=gM{3)1sB* zH7Hjx2gH=fyIn#UJhme7#=Na}CH_t|YksiR{t}To;}xwb4&I-dMrEX3VX~}hu0@ux z&Ci3LYa!>ucS13Tx5cY6XLY0fJ|PFH>ZV>yy+S6Bt%q88=X$J-cdn=4nc{n@j=8a? zb${TWAvwk)7-o2;-r?3k2Tv=JeL@DW&Xg+QwVrlF9>EOZqu==hWV*vMDSMvj+MLRW zaRj~sgbG;xIgCdA>d5SR$7N=qT~6PJp>%JkT`MYjB#|Omd?k*PA4%%zK(rXFVrm7T)dbR^a*M=-K9zj`4-RQ-5t; zOd-{^!E{}HK2UrOVn;?|VIRZPWU#ev%~c2Twp2`IyUN;_9P*sCsWZ=6 zcV(ttD^=oo)Xt~LWAnDLOtjv&!mHy`w!8*1UD#g`(%F8qK??PR4EDFBeQHCBw=oX( zF>KmS}X!=5}QYvxDob3ZIMD{q+T^MRY}h|-k)k4OEFu6 zWBns!^usez49Q4phjnYl9Olzx53;0a_B!xsbBp7Nn&&)Fe2bRlwUBTcLCgo)E6?>H zDZ1%_X|y}T5kT7|l6XGdPk*+!tiE!X={T4}3b~@I0S&`NZqCF3Dv^OgxNWiGL2?aR-0SuGH9Pa@UnPlI7%w4&)Lcf#Zpy z9OL*IWBi5t5)9Lhvr>)xj5BlcGk-4DiEHh=4IKG# z-t~oh>U>C%zY}DY^GroKhp`2hS&q6O7a3)ukiedvklc9ZSMWj5_YE4Hz71B9s=&J0U!*_fvyaqK? z&YQU!4}PPosZ3s-=6^Cu;lx+VVMbz3P-w5z;)GxqfSjymAFZ6xA+ zy!;&Y3WVefXX-ESIPNc`vcD~4@EZ6l*21GUmkf~uve>!p}$|a<)&RWpI4Q$1AX|_tAvNZ;Q^e zE#{N8fp;NUiGOlN;>DJ#&n7RXkSyLtE6wMSSYrsOCq!-|uR*yY=Ztm+nPZ(oEazA@ z!ZwmGsj_$It2yyPHHKriUb>!hV7^Mm^QfByDes&Rww{Cx&OG>LI>$DA^O>_&Ufwq6 zTzwCgy(uA`v!uSK%y9}K>wrj~PJY`sWjKUP&J0OkbbrHiFw#88_!+`MzRi%hwrEFp zIOpn@SzI;YmyMh+@k=u4@XT@NRw(S#`jz}TJR^!n+6g-^f+G;pnMT89FOIV9A{~y% zgewKsOu?AUR)CPo(bgar@Gu|a+u~81P`QFAnAF%p3HCA^9~I&`ejsFW-Z|LV*k1_l zs+`*uGJhJ6O^z^o5<$)xQ{qNA1{u6>%q4OT4ac~I9I`Lc=Z)yTn+Efew;If5Z%vR4qY>Fgbb><0T7A%Dlr(Mi!%WF0J|a;7*G$xD7)b=N{2 zETnS=cl)he!xW^Q*{TUmrX^EVlZ0iuZf3D<8JhT%*Pz8i79q&fdsyrlg<(r!&Khz;)4&igZ^&Ui)V)h+Yflt;(knZX=n)iQ&v z^?x8;zwmAGx)$p>S>%T6I^&kHNC%RLf#c*+esIQJkVtgwQ3~m-0i*GOBMU-0SJTOr z@f?+kyLbgDF`C%8mLS?IAD$5*GQ~$L7a6Hah?~3y_7ty*HhT86;&!)r)b4Ve{g2L^ zR@B*fdd5xmkmVd6YJQ>%@iewjS(`;!qkrrNM{nFZJd-j@ml!s4F=7K(@kW{C2a1nZ zJ?vcE_68&SAw*KLmDf6@Lq=dDtV@Jcj;Y2RI?Irl)8y*sSj;%=PqB{es#s3>g;;#! zSWPV9P#+?sbKQn~cgrzIA%QC`gfzCIW0h&i>sGC+C**MQBvzkTlE)hBVclw+gMU}2 z*~HZuLJpZ#A&1(PxGR~NuA8`ycTQFn>)_b!Y3GEn|0ASkmIvJ)EbrL8XRlgF;Apsz zX%F9t^Ypfq*xrf5SgzHL!%t# z0gOm%9=>y`YSBtt&XUGy52u#mbbpDX?s0m{)~yiF7FV2Mv8^0uG@Mn8Gac$s;*Ps8 z2QSGGerB1-X9VK%7yC>?2FEtyN{;POA(i93avLkx;-t&u6XGMw#I%p>AlD}&d-Q<# z%8{%jD~7SCk3|^fS6W7Nl{fDiZ zl&@KNra2gn8m62E=c-dV#eexk1&4F4sXE(vrrQ!ic`c-IOeodV*}m4&c+TkwsT}i` zgdZHkPc2l|jj0=VLR?^LLrCH*sgT@R9`s-f%N=PS_D_YJIC#j8=Uo{7kV#1+HjWac zk*>phrVAVwOEXp0b7@_kPeY{T4M$gmB=*hH3YC4PEH`-Je1t5n!++23GS3GjalAKQ z+8$idd_}`ikF0UCM9StGeEKGva5yz9WOD2_n{?pdY@Ovuu#kiQldS|cuYpa2IXLf9 zKOVKy!l69K&Rm>*$T-+TZ*K`f$xfUGTmbs^Pb8JJ&by|!%6i#c74+k;rR@o53*_`%dx>c&1CB-Pk$TP+s)H0w$}6XMwhu~i|(b}ls~&i|BV8t1c0^Oj=)8)45?$UI2p77v+kA%$b>rC;tq*Ig=uMWL+edjXd)$%S(YdA(+WC=AM_4N7lj9=`Zmbs7g zG7>FEvdX$TR|l45+(FYWKRKdVccUF#HzA27pOD415p|zc#}U*^4*%h8?$9-;?&e-d z@jEQWk<)5V6~;m;pS-X3l#Q*jX*iBlt@m6_SAU(6d&Y=phu6&o&$->|qGJoPdQ;f4 z6VlnX6f(HRxq7>}%0NQwVxAK+ncix+99ybF4rgO)um|}jBy-fGhUBm8W^2>k`IcbxJ&Rl&JL-AFb77mddr- zz#6?)@3_xTt?^JFQrnd|QeT@~44zNh&D9xkojsP?eC0S&ZDDeaOl_xrPqmxI)sD3X zci7V|laDE+$97n^VJd6%I&9#WgOG!*EPo=n4!b>4xvo2o_ zbiLxm(xvTV?>GV>k!RYrd3kt7wk6U|yIf|y(5`T(R~B-pX>1xAdtgEqNB7&#%@Y`-q#B|_oV(Y1Sf1+L=54q~LkAxsE2=UxKs%_zdl0wMhOmW+- zH+eoG8jh0-864wkJG#%Kj=uAWw-#gMu4qC!*AldJg~N&_n>bP;?eum#y_JoHQuGPy3Twd=&e%~a0%ww4ZE zPg=hm=K)(co=={%ZZEFhY&{zGPlega(woK(%O^vhVTuI;-c3d)1N zPx9j~^nLCEX{TTELisyp_uOSf3yQnYHv=n>-=L7i_FK0}qc|Xu zPeONR58WZUbB9w{-G$S-xqo;uH+Huxg%FYZ!}X)xhsYHwJ!}Jt5#w1Z%f%R6tJ{MN zE~XH93+$f?Sxnb5MjdBggv7%$(L0Vk^%x54?Jg^xBPBiUCWQxNI-Y6ri+zlq;-N0B zr&L(-^=uZ)(VoM1*9H9m30%X}v#brK(VQy#7`+(Fu_qyi{-Z+HL4P6@SGg{r7k}ub z(@Q+a(O$1Z{Z6muP$Sc;P3&*?>Kva0=+$TLGuP!Bale4xcGr;u61Z1#@7LfAl#t1N zFuFt^$I^sEK9AA+?lQng^lXm`X@|A$$sA-zPlrBPT|3J8;66;{D0LsPa-2fQNi)rT zjGYndxQ9xgZg8}y&wt?A(-SiJG^>zxSnDaS=b5f6aGh#jW*qd~@|EN9eZ@mGudh^j z4aQ|XOG_b#9x7u8oDYZ(Oub{9Vn{o?-P2)gGHRIOg-ot#9OiU$hLFnAUr6WN?yx4Y z-X2!|0>91rz`Q+dIAr^VBnZ=+kb~_%Y_c3j8d7HL%M51@{eQ`avx{}`aQl=1BK|s9 z#KWJewT73&a(E^lZr>TfMApG0_{8U#MhNpTw&*i+u#mx(<|D>+7hpY|u3PzQU#Pg{(aMkOmF7o*jA?+Z=gm9gZ2-F|; zr-r~Mu7z~&&o^qkII26^l@6mm_Tn8a6t2V+a-#SqK4t3d*TpgK(f#1sqS3>l&)n!S zvZXqP_>|Xey1+KBFH1 zk&r_)ZwC2h{7guNb9yt{1E0-s5uewb;w3`@RQ?4j;y7C+hBoh2UblbF>c?tUZW zP#-_5J!JcYgabELm${RGkdt(g#$c@}q_Q;Tx~zMvCQ)s(Xyo~Mz}a0Ki73}WvLK*&_By0u0ET~Rqi%BmoIDuXaIpF-@5$a zKG(H->fub6F&BGN%Q(mUvrIhXD3>*k^^cIr^}Wlw#8!Zi!LoN*zgUuQgmZc#YRmd` zIe!g-=c6UyN?9R+s~v?z?vNp*9@fC6&UY;>ZyZ+(uU(LPc*Y)Tn3g0W#~hXqi_hRL zA1{tH3JH7<)bc5^HWM;A_rAiu^okKPn6p-}hZ_o42+{M{B*QrBv4Y%f=DCm#_kpb_ zuYfr#hQiWv$#>@b+}eHWc?7!!!vS%Tdw-4yIh-?E$shVfuWT*nkXKHYqoAwoLoSS% z#(9iYcB4i{WTo+Z%072~KqlJznN6klcF^PX1U*+!CsakznK)s(s8)N1Z< zL&uV?=kv5n8l648)l%XUVyj>7unYZ4?7a&)+$y!Ya~x$8lGz#*QVzS_L*Z#Ogs%? zE@Lus{k|79<5?@Sub#mwQ6o!ipnor8u9*1tDs#!pLER`z%wA|G8e>z#nqXsOIbe;M zF*_DbTrVVLjD8st16NUL)vkzMsF28A$amjm_7{v1y`yF_rpve_>NRGj3;hzaZvjM& zVPuYSZxds!4ajpws=4DiFeC5B^PalhF$dQ`627A(*U0=tX=z;BtANO|7k{4aFy19T z5pQ;B1bX7e=Y2&kBR=CjgvOWMsU8ve=vcR5g6v213QQ1&*}jc*j9Z%Eu7q{byI&}u zjWr>d&hkL66yFSF_Q^{;6=R~<1kViZ%WfA&e$2k75+gDuwu@`fP3)oaF2v~zdetT_ zGnr3!pDgU_E+lYoEt9-YsDEXIq>QJDOwx)nji!_=OP5r<$84WT+v37KP2&)CH`xTv z1Dd>5J<{mzZGv%FlSAZ63kkl+ch;F)F20{>A8juf1vPosZQvT@>Kb;WG`Ss4yuCxv zH*CWy*6w?cY3KGOP~3^#+8vM~+Sy3J8bE7H?AVvh+9`>I^Ra7F2_r6H1kgI!iRjN+ zr(MMp*VZ|g@f@-^-h~r#=tybhQ(M%|)-@Y>=cLak-lu0>mp_MV?e@ugq!71K#u`BD zufTWKS-%_E;D2}g-fUcJA6;gO_c3IwEVVwlf%Wl1Qp3EA_1zadQ*8G=P)rAYUJiQ< zSif9Eon`k)9<=?%3Z84-ZOC?Dk3u1VJ1feK5G5d=hIKf_xRHU8MjM^yT#k*~j7JWR zF%;}9V&$E3e!=c;&HT2sYbWuUWc-DPu?;O0TjX!E94-Bdk>4ii zg7&DbE)AxGx-&!gotiGbHGCrzv)Hy~=PV7shJjQTvJ(zTJic#!#$m@`5>u zjVz4n3P};;qqh4ivZn2E9|=K3wm;idU0l!7viGau88soEvj}!<%NuuYgsY*aXSLN_ z|7M4H5i{%?;U3`oVxoj6Ta90hvOb07XNPifZz1+7^4$Nl>ktNR~$c8%}N+RwACL!_i z5%rIYCVD)_;-ZTY^^ZtLTZHr3w@RT#m#q^TuV~DPD=}RxyYv^4CS}i~Zhm0qL5M^= zuTGb#h*TAav<#1WNz3pv%Xaj$T#^*S{yR=WY+xou_K7p7(WQVbSD)>xYAotgmw!s& zJ78U^j!)Xkh7J+^QyUOzN*ObEuE=Xci%UpctZAzW3FELr5@)(x6~$q%J=s)OVydb>2JFJ; zYPQ^)!R?M<=p8!!n(v}_bw-P){(s~}NQB_(G&$l-RV|o?{ol4~zA#_!T22nDs9dY^ z0r@*s5)OBv)x;ebTx%&|w%oPu1LoRXzuZKB&UNb=_G%I02S&_Xcad-Nc5)jJRzT0Y zpaPL*ElY|%<76(`=QUZM4jUp%^5OS^a7OH8Ewj%9VxI*!YB)Vpqr)mhCx0);W5i5I zOc-I3*pqHTDK@my7(duZ$1ZPfOz+Xh5E3f-^=>S&5U#`_Coah>~J1Uzy(46~% zYq?+UplR3e9FcKrUfjLVt$!tJnRd=!`yO^$QNQD@m}Bwsnlhf7@|R3V%m@1`UVI;m z-yRs=Yb6em+@U>{w;e9b;Ceal4p-Fgq&WKZLJqM(A%V}IN@p8`lFyqiGoI*eBLO{1 zZ&oPiy?c3jeD905Mrxa+rMI$gv1Y+b>Qs~=UM@z!E+KN6;zD23+kck0kC~+CA$REA zzFGx$!R=;cI8*0cM!NPcTD~RAyW|2!3G}XuD{j2xQACL(7cjDACm>mj5qn>p!Q0li zKgnm4B$5qxAr}%SS^?gVtDrR~#9b);HJE`f*?=`%#y75MyuZ{ctwjF3>VKE$_SCR+HuEo~9brw3!O&cXMz zTZSFUExyiVzVaaY+!{bAK@*pq?)jEK zc@2~yLVp~eAeXdG4s~M)!vxgMLhQr{`cCy3;56ce5$BL3#W3m~vI!N>M22kl1r><6 zT||qBPYWx+Wh3rOuMjD2qBR)uuHwa+=9Rdwdw(c06H%K5Sx+3H4f`&vOqw>7YASl6 zp>!EMIG@rZ-_{vww-vJpLZZj6f}xJfU>%nb%RNg&^PywRA+%&2dWWHveQyIJ@=tLW zHgb5T20jfQ+H?a?riLD+WpGb>yCV94nx^$Ek;X1fz||R6?lV$|T=a-}+MS$>EJug@ zNq>yg3vqm2CX8${R?yf&ohN3pB5t}LbMU6F_v={;uS8dH^%f6VK)!_;VtMei*4PZSay-uW1P*zLg)iGPky z4#%B##$20}cHn4Hl<&^BN(r%ir-qPF(65iaCGI8{{fH6uX_Shw7!!(eHXFtuV=$I2 z@3><{*sFgd6{UZa$;CY_grtE!V2o)8>lulIM;&vD=;_5cTt(UY4#BFUeSr(-Wnwbd z;x$OgN3;UOzHwniZcOb;nB|FS+JB0<(D?u8I+N_mbsUIpN$db}$aeor%7AlvU)A}O zjANUUNHEX^{6*;qA!4c*v%Y#c<;RZiV(V;``%D%4ScK$?F-VJ<-B?3wBQ^=HVA#j1 zTQ)`n8wn8^&B)1Zp3?XR)a!IqxfW8`(=(Bci8*9jF_0O*>FSx41T0u5dVjmNw%ls| znQa3Zimg{4=EQCPwP+zCPaJEuZO7bYVs^Hp_zP{-Ub^Sd?rgo$A3yUl*VX7?7oO!K zr2Y5`r-<|6^PHMl<3@-l?EbXV+QNvlm|*=-W5CY%I`fb-(q=1mWM46BW#={41+|;% z!tB4@`f>DFNXpnTYB%ca@P8eMEAuoI6S3s1qCF2^m=V!iv@UPdA2<37EkryE@3f4u zb$i!4-@!t-d)oh$7U(qkmvwK`P89NEE>9FiU)0DIQn}{GKhGIsLjH|nXcL9l!0K8d z&KW3=Y&|l_PylCTUm++&JVAkr-eLTNJ+>XyhOtn zSuvM=<=JaOeyAY1{Dt!4@04#~p4N*s?rRH?dd~3+2`w8$B3^}GyKGySXY!ibz`6~w zt;RDS#oojZ@JXwOTz@4aiEB>1_SP^W69}tAcnwus{%!k*f9fq7&vDf)N&^_lHqdAC zCT9jSDK2-eV3tRSFU(qb^Za3rnYSr|i~AsI?b%kfrPshKIE^Qpc+=&G{c|pd?!%hk zKnB)0yIjB@*3yQ|7+Nt=$b+7Q5V^wmeMI0`v08{&4cB?%Q_Hw&zaQ89c#mWx{+*t5pZ-7^cY}M+>0+GEdnt+cbuV>cw8*;^ zo(&MiEpMk(~q+-wFLBOebq9tj?33BlR4jdd7`F; z?_3*tEWTI7P7%?$V(TxTiRWT_)L@{$;HUoZvxKySdYyj!Tga7#B(9nBbL-?&e{SOm zM1CX8hHJakdCEczmCsnS;CF_+BOx!RhrXFVxiYA%sDDHHJ-#vIeK^~yck@C;qpnMi zRRw!Q?J!2%zn*y}T95b=vd%90HnB>yi+CSASv}2s@SYR1lue-h*3(W8Gpo@T^kEHC zm!)c$DegIHBL+c-tA$T&kzCmP*2`TaE@GA(J$kuhq ze1%E4o1@4gg74GcrdYy(|POr@d za`Ik>C^rAhDFazRZ@RLO!}eBfV!fDncVa!R-pnTNx5!Xp9;3IF8b()yRPMNr?(L4z z$!;Mag~IWg6RzKjS;eSCf_#D7_N1tVSLeBD;;Qby{-ShSGl*f-Vv zs|&Lt-J_Bi_m0abqgq+_CDMndM2GIf>L%@|k9{T~X&73uE?a?J7rn;qH zm?gIULu?t@U>`jn$b-_yS}%xj!ZwL@D%iZ#N#pgC8SP_vh~y2#9p|5OMaMH)%PriZ@@e4B1;qE z+%MDbv4fh6J*Jdk$3YJ*MC`>DVw}nEFMkRLaRo0%&GOHz7#+)qSOr?Kw-aYc`+K@g zX!nlq%j}~MIaxe!cZAX$=D@}`TETZ9#M#TyZT@4T#Yok;u4bf>c)q5P#yU(x4tmFL zDx|Vv^~hh|IH7&yR0nEIkMOBc)k%n4e%fy%mvpc;Z)7)~ur~6j6IH54em@lKM}H}{ z@zjry@4(3RD7`M6$vMQTGG<=_8JJxjh3F=pegiy<~>*mF0={V}I&OfQLT;`GLIzJ-{f&orjq8&*4xzkj-0T*2Dk z8dXAwQhvA^(57)cqug0Yhg)Q26rw26Fl>>*!|ky8govV_==TXSD`ta=^i z`xY`tA(k*AGepr2pORy;BKS?uXan zp<9!YRU7t}j~gRckvZIJ?SIGGWW`WzX}p|x;^KJshZ@QJYIfAwo{**n@ncvO>y=o!y7wDVnF8elCAqH}=a&e~oRb;AD*W8CWmYLe;c%#qML*s=Jf0zrM zX}A32@9b7F$~MJ1c_z(FPwU3D<(fDnEkt`1f2Kt27XMDE$xwe(h<}JYeuW~2f>o9B zzv{sgJZrSN;%}?EgRypU){;Bw;R~@3&*`i+syZ04n{d^&eIIhzKv=DLMu#-|^cIW;3C;^9A#g^{^w^*h)N zG5u?k|Bk2DdB=OJtbb*ot3L5;AubGPt~gRX0XTX1`+6_~p7TM`Vz5gvJ^pV#-+A zGeOXk~b z?@}@MG558ExqpIrEG^{Z^Q^=b9rNtU-ICgfjTKC$NMgpCta)`ivrW8S2iCUEJ8keg zxVLcaq>vw4NAo^eF+2-Tv=`NH<_jGc{5!AxvPjSB#7eUHJaZIhzTt*Bqxt0LVN6Jf zJPG`!?_{pq5aOBl=(}ZNRBAps8OXTS_ctI3k@7W+TYs*vR?cg#ua%8VXnnm6%(1M9 z0pfmJ-zgn)M(ew7SWUOSS9wm#N+~zYiLYcKorlOQJhf=08_KDK4y2-uUomSPW00$C zIlFXzR)2XS*!ZNie4IaBvPTQ6Cd82JVeO<4XFZ`+pZqg?2XaC$_95$X&aA|-TB(LP ziPgkIc#k$q4Sna;*IWFUvYv_&jF$V%7Mv&}JaZ7f8oD zD>^tD?`wOB?}Zi9YB2Y)Tn;LFQ$mb&i$X!g`G1_cwTzW8LVnD&UG8FO_&oAUcv^rE zZ+J5;*Ga{$rR6(0e+AdRMkT1_Cohae3kh*-L|!_`LsmCuRsAIJgA1+%e% z@C2fD>JRnyb#}xJ2@%~H=!LG^Gi&Gz@qdh2#dV{{6W`m**7&X$!qa=#>+*E)EWA-N zMizwl$NWuf-7r3?@7W65op&P6g~&}6y~7pVh3KR2m|u^+%>HuY`RM!8J%^`$f30$E zDv*loc1K4O>eTPZ?qV;35Px{r?=OmEXdejiigf`yeKt@Xe5VxL4v}dEE7o?pp?{;y zNbaa;v~$X7k=@xMb{Xs60_oV>7xsu}ZV3^0!LOZ1YFK~0b11PB7u&MQJV8Z>U(6v# zcDth;6;ioEe@9pu^Cf|B4f-yjxQp+tv{W&#xr??N@`GJQ1=(pPr4<3OABvJAwIBcc3T&(;^ z@w*oDz8JF;(iUc<^(^^e27hWiVE@(CpjCb~{zIgtf>|peb@M$Z?|*|U2KJkIV!Ud< zRal*9zg-;b{e_4j;I4Yxc#hhBV&s^Y5n>O!qt2HK_iM_btUO2UgjzC2i_Rwx8M_#T zw1Kr!C;AIfsp|xL!PxrI{}*NiPSXF-HxrUMniq&;9b^o9%9#B>sn&%8r$9zxeu&dJjE;DfG39iVKw(D zza|u(a%B2&ZB^t6{e>eRV`H9H`y)D=5xKUIyPWpv*1-MN^nVHx*+d~aCb%}afUJOe zus>k2= z!}j(jdW)w~2!GeRoXAOF+aa)~Y-s2EOC-jO!iwP9T%-`Wf<$_}*qVt|%1Zj`6A^ukD~G zF2oONw4WLCjPNVyrCq+!SBhL0B3XMB-{{KqB7YN&|9@Do<}4X2W;K5@Km6OeJU!$G zYt@cXoFk*d)9i$_jhzi=Cu8-Sv$H4#u5Cw!KAxXl@?`EJN9&+>c=jtl_NacKVodeO zC_3hh0_n(z&l#oiU9V*i%(Wf)Sq(GALh6SV#(`waYo7D+k2804$Dqc|x#hssXBb&%<{gtxN4?eG2!zk!^kt@KH^=A3_UB76u!6%hx#5Jeq=?21z zj`N~|h<2~6wPlPf1i};W?w8U+eI!}`?{m@0^xWI`&>2Ml-nVr=7nE9#37x$Q$f zdQ|?$KL{53FI*F-;#eb$aBVu+*@|p*wuVv z>LhaVTl0@eAh*h$LK}apr-iKM)?Nl`sN5RUxNvP%Zw_lpZp-2n_%qGEhjG5!JU>45 z&3~Qe*XzW)p~lT^yYkc`A#GsJ=!!y@AIM7_)xFkYb~z&Pq_~2hz38!AJSrJJcwWz;G>S;egIC}tzC$o%ar@4!4srWS9b>n5 zgt9NLVAMAF1drydqI`$RVZc)|%rh#?+|g6VNqcvU%(V!Gri)|g$n z?+a^N@7cAXuX*J+@@yg@_Ap9tCa&Qsr;gHF;enkdOLD4eF3j%EniF@Ko$^l(;Ypq7m6r&wx66V?j3v4 zdxE#wi<)-KfrVn}hqvYvx2woQ-uFsH{`Nw182uk1m9<25)3=67U9yOUr+&S3!V^U# zDsFHES+)hesh4wNt}LW&tbf*uLRpT~zdXK*`baPD5H>_arx7F0LUb5-Z7&i{ghcBY zM%|x0;|Al@P-n37a_hLQ8tL6(Mw6AvYYkY52To7-~S9ND07kl-#4}YuRUrTMF=lWXB z2|daeV`#C??lpNvm;(znJJx7_Ep|4pTKr^T9$mg+IPY~+XB^2P8demmmB+!gcWA$LA=ru!5#t52+|V_$*ZjIp+(m|)FVx%fP^ zEAW(rzCWK}dAnSn_<#Ji`Qx|g3}bKK>$$Kx-s`1sN1~9(lOp0ZVJ7nR5sI^fjP!lz zxxSJ0JZP0isg-xH{KkpVh&OaQ^Ibnq#32z0H952gGYZ=Ac~(xH-}TBdtr-z(75%KY zVjviP({)KLVfWe- z_jlgcp{<26ho|FsF$((hxjR-&KYcc=LDwM-HYB32*e8v|({dB{S#Px4QN4E896$bX;XQ&P8h4L{E*PMtwQbWZt6 zV@4G*{6MUc8kUe`*ejoc3!);6kd+E?e<52WfY&A)PZ`|w#kgwnLId$bape>vvp=eC z3d9Q8HzCb1za8Ckp-06?a*AIEQZSPeh+%iGkXr_ydV3#E!3f3s_K|NUBz=Q=iiNbY z*^-N?Zhu~zuIA15x_^c>Oew~P6!OGVjHlS-<)jz^fp=qyp#T__(qlGlIo#JHf)V&e z#raU7Nyw~5zGnJn)b8U7W~AD1zX@^jON8_fb+UwvgnfE})WcSJ4xXp?AK|k-cf+X! zDa2<`?N3N*hjJe&WKXaMDuv+2kKMbv=o7AO&3{mZLF8JZOn&-Ah!?4H3K6FvW+!A+ z?8#5j^e|~6W{Q&bTksv6QWu}BQ$>|=)u$))z=Vw2f2?5YPvIUtmn)S5DTn#amBT_& z0wG0j2fvR<=OtZ&6eTj5RK$ctRVDu&|7FGQ%9kQ$ft1Yl?dfVH&q#>pO_8}kd{gEj z#eaY*jHwEFxlyfD65&aO`s-7e*n~U&&-78gB*g*RRR1PqmrW6j{|LoYql3d9DKH2q z;zEqX=udo}WUTxzBwSberYSdBB-+<0Ps3V6E$z#NA~Hfs<+G5=XcO6bDyuAH-l-TL|nSq>H19U<-%PfQUia;jw$V*Zh) zxe#64l*iD=E-@7Vkydd=l@O1K0f@}jDWeuU^h1TSlw6((dU_!r+^9=iNN<=;5;96b zew}<}rL^15`AiOf>iac!$+)Pxbz z^5A!{#G>(bPEof(6C}gg`}vikXH&@%l*|#HQPeO@JXdg$SOFK zM#%ke(nKH}aTfBblVU+iCsLG*`1dH@E2Njox2EV8>VgW%1v}ycsh!t$YtG0m(;e|j z88P-)(w%K#6erzP7e*t5v_?7c6ot&rcS#8UQzfR578xK!X2qq1%vJr}~jGQmWG;O>~gvi3us`H-?AOE&v51CNN?|jIs0zuty{d@Zid7@ZQ z9;ELgM1B_9s;qy<`3os$t&OIO2`P@fq$xyo3+ZV7A&vA8ghUpbzIIIEa9%hcNER{5uNva~;0zc!esL zxBc%p^2>zTq{5pvnARxRlyxOn0hGPoU9@QOhjc{u-2*uKhN4i2>R8XhJb-Xx3xRbO zW(dJ*G;1_rM3&y>MtkVKx_9(vOTh+C*myLIx+hKdr{jvCu>?e&(rTivw&38G7gz(P zIBO4Da>4G=tRr9mf58x%HU%zyMPr^BUWF`m938lLiv41 zd{c_KHUML&u(}wV9j`GT>z$O#5IxhvyuA=B`qEi(R|Xee9CWUagb*3I$NPM5{p2RG z^72$Fm`1zzt|L<(ROxv7Mn)Fs`qkSIpCb}!gl)ayuhfHiK68-gINmO3=OU@syQHWp zAYieCDYVxCVNrf+|HyNo$rt)FQDcw4G3FVj=20O{D4fD=A*E8UW1r>vSb&`nq3{r=#VN5F@*?)|E_1-qAT8{*>23k? z!V(3h#4pjjoh3c!GEhEJ6X_OK@M@#hPkI(s5P_;v^bZA{EU;T9MMSUKb1eVNE8v2Z zDc^Ind`(7$J5Mh|&9g~GjhF9!MmYa1;phaW?LfqKlnM{#SBK5+3>r-v3S~?l$QL$| z{vSNeDXz zCKp-jYMAV3Y9r4{>j#Ln>Dr^v&Xf(4xSZQVf$RR0#IiiF|2Kt>yo2aUo zynzU-ee*f$0zNr{M@93&t#^ zaG%}SwoL=Fk!rf9Dm&U^ZTayep2Z(=bJ1sIS$5IKvp&wL7M$kO@1iS0TJY9qZQa~W zLiq6{skT~Qq!g_5l6`Y-sf@{>D~QFdxB0%IH-_IF05kQIm~0%M7dYSqb!&066M;_^ zZ5bWZ!Ac(`KO3gMn=jZ<#tblZ7X`xu#X&h4rQkx~Ae27#lDJ}Pbn8f$5T1Lu}N`%u3NE>p`KpW(vzb>REENM2b@nVyt z?;>H$4MB~4?VKyK#fhkq9NQCL9a1*d?4QLk$xjg*-Wl`d1^9Gu13$TO~&!gmCzu2XcC}5uDmRn%>00PydA$M_fv0!lgd9utd>Uq*jwj? zHfCUYBX#?_MalmFrj{SZOg8erTPW8@GD%=4Jej0rXQD6kN}&+m;5WjUmm1Rj$u`%< zdjfn~=IoGjbl2mwvp+t@2A9Kc#MvgT?ASi_QC=0Gz9Jnc+CZSYkj@LA3EPAg0UYOS zGxr7(XTWcCZmsI??EaTsYKr5QZh~X~;v){$qvD8n#b%WHR3$3AZ*k)clGVjeLTK`7 z`;ME+mhuzor*G(3BQd;5PL-aL<2vNbQ@9dGBwj5#PBW6upyDd)teY;!obyO~jNxNl z%x>_@eNyYCkMwgSruNc$A0Il!faP-$iEs~7{5y}+uXA}rk>EO+JW|;iz3?EOYdod? zq{f$eiFQFEvag=(kn6wf{k$gsr~2it5x)6}NaQg=@s7Q!RTS#&ee>t%+DPXCgFn13 zidNZc$*18^)}B*UqqCzh@!YjfB0trajw?}3g)&M~lh3L~ZPyKdPj_^NHu3}w60zlfW*8MD{DyWJV0!Ce=dD~!C^z(I0$3ID?(Z2}DoDk^N zNOS0^7caO0^@1H3qRA_LI{Ilt4r6C9_WGK7^-(O3I>JegFV*e>;U&u?Vc8&J)MgwZv|q>^ zityB=u`!LTO!zBbS5r}BqLP$g#o(YR^!N)RX@c_EFj9xt+7}&j-O*o*U?;e4)3RD2 zg2dr?#6;uu`labcRB@GlN7$1*eB{8doW^p4M%92#2fEK1iP2%l?kG^V9BK}H*LX_p zSKY6ao`xNKuGDhg4kwA0Re)=)bJuv^6Fo2t5rVy*EuZuZVeHy{+j1nIX+G*KtT2-a zz3MmMvA@@AfBpWI!HL;jWyBc{fP3vaS)F$%TWd7-p7^7@r325-x#--8<9>dq>0M2f zXEV^d>rk_LjN6<$@3WBw77-!Driz}y>j@OfUVJZGVM>`my9p#I#np|z{!Bo@X~ zYMsp)oUfPOhS=*mQr}fj6v!LQrG}*b2)t}_)Y31aUp(3gcu~D& zzY#xzUv1ceny9Xs6M@KaCeVbvD{~4?`wbS77~&+v4j_&S z0Iu~FP)=l|uD$Q^#xFV;lGj=c|6XQ z)T&RnjuJ1l9ZotL7U{kW{KHpp=`SvG7}`>B+dIz>+le*#t*?IZ?IUs7-^;f7YAatK;Rx>lH=^a)@f_3oJmn3jK7hf73u_D_O*3gXB zd#D*-z0&xkg0d+y@mG{L8b=Tt(lYjrG)H^t?f#$=@?&e{9Px0~fH(pzszCdjqn z+guXgI+nonwCRJrI%fUCMIbtMa1a>~(Rr7vU)iKb25b&(?8mcx68J2M%zfR?)j68`!B%_&IV`Aw= zi(_sBir-z(A2WbVWJ$tQ%PtqMYa&Rk4mLtPH(6*4zc|c3e^JTIsQ%-ZewVcHV>3F* z2G{Ce!kUWHW@x3g7dI+qfco8Awz0m>^k5>DjA@azlb^&zO2Mmr8*ct+cw7kU^Fb1Ah;3YO%UB6gmOR{)CRkM4Y<~Z zFdkgkg*u(;!|s@36N$9$@xpX{k(bf7zO53K>p4qnWX=Vrr9;qj%{E zYPbG0dCt$&80Ru{{&&rGFAi?0%*5G}HrJA_8EO65n7R1=;UhSrrAEBwTlu$oK+qk9 zanmgf(~V_pB0t%Lk@e6AYfS1N*wpwE`MWSd;(L`Cku*Zgm-{j z#)rhGU;=m^JQW>XS7>z~+IvR^^ z&g@fO_0>I}eHL}bw$QH91_;`Q^dP?p(>%?4PnDD=IsYq}p>&PJ%>P{kE)89oU)@l;Jn*)iX6KdHS|5j~EadRe zJt*B!UfXus1gr*~*C=*>Y+W1-b)`UvH$x4+@`oos8bP11$&&gc;}x5QQU0a4!JP8) zd`+#1X1xvqRQL^i9!>EyJO@c{na%YGoBtI5wV|1%Iv=@29z=#NHw<<<=F1rUZ*yW# zP>ttsV1Pk`G?vOC23Pk7YfGipsqF8I%3R20eDS%3Im}SpIr~8mc?yy|o3+f3xe;4x z|A-HY?L%O0gl$nRinEv;HXZoUm6JgaDG(hKUR(({^%^ds8{hPB$F!JDGD)>5&2M0B z>CEu#TM^TbltxXQN_YJk^|5Z~>5)*NO4?ZVJfiLSM->lavW zAa1n)MKj$WzdM8I^@&HzR23=*Jl^x{un!rj%F*!`oJDC3A~y?VqWi5yz`L%rfP7mN zCQwL26b(EByW*%3&UBN!m}&B5JENWB>`iC#t>U|*zt7aH%NyMUvlaW%-}%2KJ)vWv z?f#qBi!wx2NB&!qT@z~$3>*mu;YrvqSH>6;ue(P%-+o`FVzvk>5;T?N)k~sbtG;}y zo*2#Z4Q|%4`x7Fk(IrUM8-X+cAW+jsW|BP|b4xm^*U?Un88stR{F5b55_XXB+$d%g zPZ265+3cV_LkmGY;Yw(P3~u6mJrfXQKHqf67-?hx*S~Ncq)Qj2 zDOyNz?cFps~9RW+`Smw0A{?}{Gs21F#2+3026JljXOb66%a`W|ImdDuM zrLu$!NP?L?5C!f0(MjGimJE(EeDt%n zyH&SZsGsQNsF)A`x~-PiL9u3?-L^rarUu3}3}%|NNF$1WD+28{UI};X{oFs7oD(EY zXtDq5QJon0xytgDs1~8%t7SIxexB64jVKDxNpa$^G;|2yiSmX88W`=+N%wytbi9&0 zWmL5K8DvEN0__J$@>%3f__`rS?_p-|X*8pQIv;2h5S-xfXdGGL#(I+DX$E+Isrk1c z8JA}Qz_dOr$D?hvznGsr5gk=9eXWCqSwiNuT;6Wk((nzcM&aVBlTfaPDo^O_A{(h+ z>_jo2KNBEOO5u+g(JC1_k>;t)@gZ=vwunhJOFX+y;0} z(*F!CKA7=WsJGdhfJ#hOeG}p&`zy(ppXknV@&V2`B^DfNStM8eF$=t0-rt`aOtLLq zIVq!}WLCTX8(@-`iF%1U*Qx2g%75Ns`(}_wuUc;XEwkwtdk%5^_fD6`)S{@Hfi!2U zt2GIz&;OECcA6$j#@jsdD9SKmXy&y;C?VxQO>JaTdETh4Vt7>RhGiyp_qS)nf)=Ue zHlRp*#n|{_exIhZnaOVo@oWQhxQ@TnZ#x* zhf@bl)iL{rZGE8fSMbZsmHKW@@@6%=U8WNv#Af-C?@#5&0@-iS9AL>ap_Rq_Kyk^l zN}n#Dfj7R2)E{(n7U|p%Q~AWTf{U%aqJW_qRvU;2OsM6!|9f?JkERY*3YK83H~Q7^ zAgKj2Le-p0g*rgES4m&`mP(?suD4?TF3uR&=cfdZhcFIQD_%`^AMB?uRGn9KSoua= z27)-RwMsfE;l}c6sUGD zaWM{ZFCs=g6hqqwLNK@NJ~fE(?*9EXJ$~Ej%*EGB{|${sr)@2C);GZLRG276c`K?i zDxQkmjZ$}}sRr(8L^@!U|F{Q{#EN&;okZ!bP^->CddcrhZ{j50NpiwvV<#N|LCt%K zY^hHDXjw(>xo-qn0yh2;bG`PE?DoI#Z26JB;g+Pu zIGa(T-Zv)g5mqX6S@S0{MLjtADW_$WeFVe_wYrO`t4WwFS2Ifs0L#YZwzNcvgv8AL zqI8_&{JNx)@cG|~`hr+t$6>C6`J=}3S?`{m2fFKCPl;YtrDw;VrVe~sfrwq3DPuuc zO;&vDKOEEKUhYosfWvcbs_vAU_!p2Od|1D4Rtlki#!a!cO%D=qy>F9*x@wqbX$xvS z#%hu5kd?!4=>w zvD`{#0)F&%;Gz@Hf=E%a~M6ZQT~BZh9@fsvPtjJ) zv5*~XeibJ*{%xZYySurmE9b>9;y|dnmG|wqrf~3(4aH7pm_nXWxzH`+l>YDjU2-1jqf)7Ti|^VBwBQB>ep3qf z-lG(z>!QvBF1dYWw~Y>iAaZBF#W0>(%aw*|GKoEs7d&4ZYpebEkNsycgCeqNWTACA z4q8!488xlJEs3XM;Vr7HfERLRjA>YCJ%v zB{qVkk%G(tNfP#)`EuMWFOmWcI}WA)fQe?j27ik#_qmq0a+c^$nIPQxJ@^vnKoNnu z$21Q5VW#TgH#iWQ(6kOT? zfNR0YsKb0H;8>p&$i=QbytC4X&OVm;bkM+lD*9v+5#LwaUs}SiGM`DL#meZg-JWR5 zbdaAC75qVt&8#aSznTyK<<@%n?LuHT1T-EW9Q2*r^d*3CcjUK+{8dZkH)9O&?fi#yj-DSQ=lwgc>$GBb-F%&WtI^FWY!B;+Y>O+J3cFh&ynr6u{m6Sx zYU8@tqad0rk(VSs3j1J>$g__kR)#}W(!tbIb|FRG!w>AMQ?#p_3vzk)>)L`Id@sqQ z$zE_=zb%ex`1XD;-HHS=7}9lTJfLNHLHH!2UhGP5*ZZPdw>Usc zq-u1N-Le^jjF@?- z-l@|ev0mhp`HZt?8CQMQNaYt92d3_n(m{-jroVd@l}3E6VU|6)1>nZG&cCp~eAG%( zD%WbLaL{uRj0@+QY*gAU&GD7IrY0AV3=^K${Kwp#8Ic1eP5PY6cnV&G>`L>v{&1nK zum(jIz3dzxfVg;CojNYJmK*R>$sx7PZ9Kah{xN0@iCxF;3)o>M503jGVRRhdZr8O` zo;6-BQKSUiN?`N00I~|KSH=dED>07m*+cKroD{ zmai%Od^R^Z7d!rBOx8<_Q|=C<;Vn*`!=!Mn`ee~#W;?na*3N%(GK|MTktT_0Rp~-J z0jc@w(piNuYUL+u5g=EufXvM|zR5<6%zvuLjg7j#1WiTs;fcHQIhD}2I2itpXw8FH z@aiUMn@f8EGAxaFzMOX~`9#K@c*C_vyAh_0a0NQdV>jd5&59HIhvaW(wgXRe2Z~r+ zAHHgJZX;2ehLfOBc!pF8M*b$_ahhe*H~ko_#%9@$~w5^$Zz>WXmoxJX5HpM98HT|N_AZfeEd5`Evr`s_?c<0D4m^D{um*_sn`}y3H z4+}u}Vv~sP+GTqhiw`%83tE>N#FaE~QDDw;)4Nm{=6lvUAt?t;=x2XDiZ5pH-ykI| zde{7Fm21<`yK!RBbA{1kSnl@xXdn-gx+CZ*I$3w}06nAg%j*6@b!y%`MtgDRWA$>- z8nF5iIFk?a%SrXwxHKE|bae0U_1Vy+^8%jq{dT!KZ`@nam4xAWUif@l4m-GpT3l1t zm!NyD!a7}^D#-uqivwS~4NqMymSN{C(-1q^sDNd=JViBA?a9gQaL1{es3dAjB4sT? zmj~7ty8UVC=%|SUlzADyIc?SdBEae9V&z3=taXS^Rmi3An)x#|F>ie{pQL~A3g8{* zc@vpw@7By?j)cgk6c@?O zW+JKS8d`D=ma6e;cjui;9xo0oB-=_KLtxC$tQz~s7c7^io`)MBlP-1a{K6*c!~@Y; zW$kAh@dO2+CDw8c5b{~y9^PRDfOK{4)vcK?GIpHG+tL^+#4>7{nGM7WRj!G4EG$qL z;?O{&TNN%>mXRqLvMVA8~l9VYs8WVTj#J;#hs0f{S$7aB(ZRTfpl)xV$sExR0Ljw^TsT z2;Srv-bnk=93xcw*ypJl?ZL=Vjz&gIiX!Nq(y`v>Drs<%Z0TbqAgg-_D?Mipm>n%( z;d>wT&sFw~Fs!rb1K=blXMKb0Sg(4((E)vPP`|H^F_jIM3LWED?-Yzlyp){C?y=VZ zbqLGE1;f`snSbx-R|G^eawCyh9^Z@@BeMZr;5)|zj$J`qOX-8V^g z%zAaigJfek27GYCJ{;DGt+}ROQ^T328(XgJn%Yab71>^I4{!AvXMKhXJn=F48_&syfETOs;oj zSaThxk<{m`UiyA)3>VWj?;X?g?wM+@_YNGFmLLyK23Q3$|LW=g=;HxNZ_{}CMMh;@ zu5aH_&edM$aIu5b$dVx>NQ**z3T9JjU2<5b^yS^%$i&YUhOeS@pToi( zjtTKb*exQ)ivw0I{y;(~Z+h@;m_4mc%+fipVqH+rGF~3_L*gqxQRUuB8GQ3I5S<57 zY#EQi0NI_YfnX1=`0gnWT<`h8b(zx<{65p{rsraF@hAPjyQ)Cv(9=9})PdE@4(u0g zq_fraH~hohXz*$9OulI9pxH+5J8{*Tu$T)TNvV|&b~#GvCFZL~OS{i*N^w&Nt>|%e z$;eQr-H2O~XFYCVxO*PQasW91EQkFZ^3I`caW5;6Lo^;oW# zJ>+YZI>ASbBfd)W(?TY5gKybkn)<`Th* zp@BQ`5^)_G1gP@WVS)I()Rl?yCAI)y8+{u?|EZl?@zyN9iw?`5pT?UXmB>o0Jmydy z0YEDBiGwP<^(J5s5SOhmO}X-!z|nAlnxogNQQgaNt+Rl`_+)u*3#HDS4{L|yopsXl zn!-6N2B9om+w#>g(Yf-<7`kr56R$jbu@0#cu%^54hg&E_t&?l^sPAw0GK?OFtgvgyqVI! zmE-9kch>$@W`K?(RlekQs*bH=RoWotTI%bricXj*UC!Rfh->PBU7DD67$Ymxp1dmo zzZ>TIk?phA$@SFbbHBK}fIF6G&etxigr>xIo6PDcANY%~%w)H1MLansiNH<{&*H(@ zw&3~_Sm9H)3?|SU;NKO8u@L-c?WY#&8ChQq`5jEK!s#Wvnhu%G`Ff^v^ozTxE!qAe zb6oI39>Si+!k|`7uLDzysdiQVz)@*fb^oJyoS&%x4giI?q+}E2mo+~MsIE;Ss`Uj;ZI>Kmp6s!5WsEnCmb@4dT}8#u@~d-vBZm-6#h~K0f`tyo zb>Z?N#ZO5DXwP52tT5+yZXGbwnPLZJI#g;JE%W+Wg|$LlUqk6`ULnSN?V1&FIpN!$ z948_%;v;Ap%o^Pn#WWBdcjHFKg@Mo~cqVm}JJ3`2QFJ7RjAz$*$WM9b7B6d0P+wM) zr#F#EpS8SAks=XhbeGR4@gUOu6EAmD#d;9RI?G=!Q71On^n8k-vFycG?C&u7c%l{k zIz5|lfi=k@YFD1J+@AC66yx!^wP!2s6#hz9be>Kx!AD9r4ZtQj=K(yA0bc4sArbh) z9$@OW;Yrz~%a8?`)}AWzym8umR5iYk4_%ZB(nP^RsOee$&{?wUG&BqyzGzVy%~TGH z`F7-hvz)xZ+R1r#MXJ^nY#lmH<&L?eJjD|@!y~WA;Q=#)*-puH+=AgH1&VFYT_mw{ zO;a(1=`G*l0 z2PUyd_9vm0pkVQy0&mERX*{LHm&|%mA|+dV&ySzPQ5aI+i9ngG1d|PW3ck1Nr9UuA5`~0d%3L(M2sUe7c|ZCklGv7b#cx)dq=K`|sjj(_CSG&?!+nh`n&MwyQ5@9HU~Q)cY zM2_m5UQCuvy(gaxCP>3P->Ie3rXN2*@|@%`?AhI>J#c#&WMJOp1$>QVF#feH7E@D@ zR82CXjD1MFI7hgMbfaB9U|^2m4)jKmCfn(ge92~4!Bs%9i>-|VU~>OHp?h<4so)mGX zuU!~o9Hp%x#eM_@C@wtvb@Tsq$T1rKD1hpRvqwfzysXaHLNNwbKNxXi=+%8_9FUaaObD@;!-R`68EFozI13@ zSYKCPh*-6_z1#+duxN89-E^eR(N(68mN^Xz&%C|Slkq43x$lPb^1)0Z;9v;cO)w`b zr?f5OCb;t>p{!P$ZVJq!U;91$8mH23$HzO{>b<;~veDNPG^`Yg;e1dk50nhOUOFSq z@)^4Rlr-L~W|daAGzB59$jo{80%nY`Y(i~v8zykuCa38Lz2(qfOp0un04z)2pE6puFZPnTh2< z&lsd7!=iv}kN~e!rXx0TnTb&bLAz}Obbn- z_J=W|sUab|x~Y`aKR2!{P$WLA8zu=J`J(BCbn+`={CkhL9vjqtW2q%4<(jTm#@vc^ z(=`{DeNGG{DY~UpFo*xUm<9rInDtcef7D2odU`-3UBQOm1pY<-69CzJ8mI;F6i?{^ zg0_|KDA(h?2*}&@?4u=9r$u`^UVYc@GYg@S#YNPa1<1oYxeA3ZMg`UtysZhE6zBDK zHkX{U5Q3)&A?eJ`mf6nDkS zu-$9cra_d({_V1o-^)Ab;-L2Mlh?U`ThpT__0$}crcse||-(VAWHvjieJ-Ro;AJO@n zV;;wBDtud-RWCE4CfT-}j5#;kY58Qu$tGw+mkNR2C7%B=YZ|FdilTuB(w|~3_m{A-Km(`QLv0sv-`jovJKi|=P--klqyoEXlKY@2YK zCw8cOx#(fo6z_ia-D(ic1zvyaTUW$S+_i+88H`DQ|EMj2Rw{2l;$4l9bV9-()81t! zx(zL7OniGe91SM<3LkR^TVy>OZWbHt{FAaAq*2N< zE#Pr%UqA76zG98xMN4qJ`810*MtiC=L#M%mPqNzUlN{gs2v$9D*OGMWu1+M-wA80W zY(~aoNrWzRRC5i1^ij^-1Nj@N_ur2zb(Lsz|06N%sBSQ->6;qd`fBOx)KBd88T{MU;L6An3wXWmzCE?6(6yv)6`#z%Oy#RuP3!t) zBnodUS#;EtmZ~tC@-FxjPA&Cnh>yPf-ln?4i0Bh&!Pe{WzbAR_u>jQ1R`npz`;C$Y za-|J4fG6vZsOmZYo+5FIxT6hwpNmVXV|gdbtJr=2gCr)iw~1E4I*UkRryVz) z9Su>BD>?NKj7@izw4GbN`t}D|ttR%jR`t-oxy_WH=t91uO9WTUSreyyvbz_FTDx%* zn#*6DBavIVu?n!s0j%DWNu+*fHtim1q0~q@KyYvk-8e*HUdouSN8B5MEV3mX1lgxo z7&FwCF|Vo@EGUn7PbeN)yFK{(P0W4URx`}wieK7TzB#{kPU7mjXX`@pYS_&l z+X5CKWWMFL+^gQu8c`oLD#9MQb_)I#9B`ppeIQFMak)gObz;(tF z>Pa%((|<~KJY9eXMs@e{ul{^lkd=t`qZ+z{S{H>b z<=AHU3g0|bH8+G7-J|iee|5ol4>kD zJrv#+%JLN`P5`MHzc3mXg}9;0U3=z zXPT|Se-FYOFTJ4n98}PnEzfQPLintws?r%o?Z;IrtP>eKvxSmn!Gon@PhAt60&CSP zc|aIvB`5NCNBdl{XAzCl>0*&{sa(?gW!uBjXfAu{YwkEeMM`|@%>`)}*xE28A$+0g zCVybWY*J1UXF`KXaFu{rl`g_g!f07MTN3?-XrBRc@PXU~F^+r7oMT7CQs-al7Tbt> z_1v=l?5ar0K5IzC5`BI)@pbpZV{Tx24JhF&_;mX0bS?3coBIV%|HR%-ZpThdltjPw zK2)aYCERW9B%@l+#t-deLx4K+baqMCWMB2Mva5IMc(Ioqh_4b$I?A{%biR_aQ|D+U z|L5B?bQCFNIm+@5|(Nv2P6jA zt%(xfUH7!mf~2l%JeuF`-_JN*1dB@D5&2@#=H}qv3DUB5zvBQasFc=5d_-n_fN^uO z|D^F1rH?c7ZaO4t#Mpd!h0h;NFm8FLl*!M-YQ)fth;3~Wc3MW zSr%(^<3o*)!Wo%6an3VoKFE>+cf2c#Bk%EIuhG)#1ee^HyHJh6(DH>N!XdAr$mD}C zAARIgk-Z(zf(O5)(DCJnqSY#sAO_JSJW?xJB^2fSTh+y*n1OtmaIfw4Ig6!G_y!6h zF%z(t!6e_2asks??;llF=E`Rc@t3*<1saUAsqzT&XQ?17i7k>J@_PVkVKZhb=8A+( zxo?Du#OP_XEQTDgIlI2R8&9L^wh0Ayc!vg71t;V3d3L3r=1V8OKd{O$vBRY@-NKNIbwRD z&i2hX0m2Kj)76fh^N`{alI3~)fPPGA*F=i3-sXFzO21`nc-a=lx2KrH#L9)ssn zW)^^Q1E?D#A2)@km@v7h3k2vAYn#izet&pg6#MC)#tNVqCcb( z=M>^h1#;AZik97e*qzMcQy@GdG}@0lJIAWMEc1kK%Kh(BE1iUwuoC2czY53L9{axW z&E!Z!j%T6_%!_z>kGL}W)PuAeDt>J(1PiT;8g9Kn&jGJHIVZ`A)9HpDbnA7x#~O3s z>r*b1t6xr)cJ6C3^TKP?7(|Ew@%^tEF6?ZmTu_=c;U~ z>zVG(D8F%kZJ`!5UGzFPAU`8%uj-JuI6tRdG&7KX^-8xP_v_TGQT%5tE9Q4}Uo5sG zOH)?H!320@7H?8tABl)Kpn^)TeR_P#U$af?kXEPmqI&d4E!{|7Rk*%Ssmn1W!$vL{ z-N)3c$dC7uEXy!bOAaWm`%R8M^0&U=ElYqtN)$1+&Mt0!>^-kObN{S)d%{;zho<)W zBTZ#_5xz9Og?sOl)7RRqdbaap_daa~bF>g;q6ffIqjeJpe~am$o!W)$dkRBwYHKgeo${nvfkb&23zI|r;O(tY&B=xwX9H!QyVmTS|#;R3Eq|O{Ej%wv8 zAQ;5O4Wkr;W0S8+eLi$jCCWFxj3O&`_{Qf2cpOR!jN*C5;Kve1?9@mpCUa1Z;(I8T zssI7}*SfI&OJB(d6FKB~-!6Q|PL(KFUWHiqa#92szrk_QY4PXE2b0cRRW!T0v~c&T zphn;8y03~<>?Ry(%q)wMO-&co4fZo-EebZ98qSI}%wcYsb49gY;$5{xR1RwMdb}yy zjQc%U;;w9FS>*1xLLHFPfI>~HFN}sxJ&i{&Io;NP+6Hf*$FiD3Yt7sOwd7hk0L!H4dNSpAX8E!|X z`{~E+v;-grGkdd**C-Y#yN!KvE-KGN9ocALgAZAY8}^MXp!V}!S9lap?Pm6K;H4>p z(_Ivjc^#7I|%ZmYyWb9y0SUN$)H5+ z{^+Ur!3y18$XX*bN}Fb@?X;Z<3;nd4Tw#6D5+$_)wTGoDPX9TTJYtNAl& zWQ>y7`ySMeTS8)-ytL?`wcoV}DB1l-Ap#Q{Vwh9PeCu)dBGwh@#31x?+M!7zL?AA6 z?p`GCX>5vjcVLVFe`SLNHU1aE+nW&^I;)G%&utaW*ifPN0Hg>JlexD7ovP&a6W4?g z)0DLF7$*M0Grveyh}@a9IL$7rN5>=|v}4?}m+C@2_qS%zJuS=vku3OtZ?P?=y0#~Y zp~wrN)9p9OC_+_r6&xSdoP@On@zwef436#7kiGEqm8E?2X6)k{b{eUiFj-kWuf)mR z5moE)TFz`>qt>S~eZq4q(OOk*={Ph-zP@Cbyq~3QsQ}wSuzBNr0&RqPPZNWjwh`+QqhHB&u7TSM14c2SCvdJSVelq>fnB-U%^Sx#?>#pIZyq>)<|bnWCMf#RCeu8xOfJ14(SIOz3T- zl1@-dCqDlaPVRns(5XzMogL9OO-T0p?_S8fC60~5Cb}MYuB8m??_75#bHf!{>Re$8 zZOD_P9O;yn4aI>O*SmY$d$u~XzcP;EEZ}b!AB9;5`(kLJ|2q7@T}_o-MuXL z*4I2zbQP;dg!WZI5FF55j?C>$_*Ok~clOhezelRs-KJG7{chdlRz8y!DB#$Ief_uj zoNOqFMe<5fA20D8Jg1CssiA9Frb((~yz)q+`n6v>z?ZP2%FljUy(s3A=I8Ksj@Nk1 z^LWMvdL2%46xNKJ1%_Hjyf_Ney&7_7D35^4w{BASF~)@)E|^9Mt$Va)()HN3Fb;6m z<@j%e8oC&_c1w1P*m~~XJ%nr!U-h<@>vh}4Ir+6fDht79k?uAEo})5<{-yo!)8RT( zN{WsGZUh(Td_FH3upKRALOsW;mc(sL#}Cu^z$kls4)UGYmV_YHK+=@2m(RJ_EfIl^vYi#5pr{!5$`3i;OU}IeR3bQ z>sUiR^eZCP2PDZacC~d(-;e8Ob=5W)cff9pIX*Y!_!k0Rl|5!y+8cBmalPA5Wcv@m zM3mdVjx`#VL3ewN+=p>p%eGL19~R^De;>qNv|&esp0w0XoUy zwXD<41pYHqHg7vOfpV4?CUByknQr%TsDHo|J_0MgeL?QS;0BYUSAQXh`YhI`UL(4pwMP5;MLt{p3Q=0jjFP*CSqpAC*vClS(6H;%Ijmr zAFB+NBNi3Efxy#ESM=}$0S}XmpC=K*T{e721XLi0<@jrIb|x()`r3XEQvemYcK?mM zQVbID#SdfG@#c`j`1#cDBq~%0PDMHKRo$(pB$`pP>2{J&Y8OF1S~seK^z5(48=>eX zUoyKS%&cWZ8z1hv**){-S9X*B4shmD6??u|z4LmaLMXuk>NW}#s^l}O4LIqs<98vL zlV*J_NfT=pT;3+=qnDfo1z2o#jud;eC3kfb_SpHbAup4}C3AvC3)XQ=-k+nLCQ*ef z;!`8T!J1`(;wbg>`pA@$!O{1te1>Cme=Zq#pqEw6w(YhdmBw2bDt}4EwEqLkKs3M4 z&S!mKFZPi|JX!9@eb1PQ45VSaSBO1iZ8+aZA7-G=r!Qq0AAJQl5BWsu1G9hSM}qY6 z{+ymNR>}&I%D{IMvaxdU9J%jUO?UK1KcvgG=ptl9b|QX{>}%%eIM-{&?DDyzHO&8< zD@z2~%7pZf@4~%lVf^6S=>Ei=dpYJkArfQN@diJuzn)h8VWjcAo(J+^oHxs5=^{Jc zuwvl6m6%!nyf1Ma>B_0+Y{h?#=nmQkHre|PGJ)wOr$A07U87a0inJKLJ0cX)TR%(rAWXKseOr*0}*0eht%y8e@S z%@X#LKgnMzgnJ_I=Rc;c3u%dU$tMdRYYFZb9sc|~t6shf-|>x84e!bMhb}qtN|0mi z9xolMMDJPk!&rakJ*PaDA?Kxx9{>F-T|w!8bF^+DVrKl-cS@h(yFD4V7%35=V~ua{ za^v~ik}NUk0g)KEi|0UILC0Pmxg|P^xH_+w#aiZVt5}u%m$O3o_LB93tdB3ngaQ7W z>fD#}l4rx1-AmmEu7Qwl7?F8t^o)M+OLBx!FGMytV`YDwHronHzCbqS0d)-IAmfV4 z$4F=eyu6IL`d^ksjG3;NUnywezoJWmOaf0g!LX|=kc~Q6j%(tYA6a77kf-FuWH_uK zdNCORV*xLCn6TUUm8HwgUmBINxIf`#pbz-01@q80td%IpKas3gOP<{R>Vc(;e-HLt-;5n4c`>3i^Z zX7GP@<4K%DCjsTb+_fP;(wnC(mZL#`!)Isxs$F@j^vA0s2drV1^sU*s((lOw%WOHU zuW%pbTM&AQ76*7Lpl zg074aU+6Qv;FQJusgTAT?eDepp>HO{5^Ide6<3KN}0<%Ab-JPt_jXj3#6hp6EZulfkj0La@+(`C*>%G$djPT zd6V4pN8Y;-d3w}wAjEu9=ZTP72GtE(u(B=6mk5ztPjMF^D`koe3OR=Uf{<&CG_4RF zY^t*}EltC?rI4B@X=fobHOff{`Rae96CduRp~n(PN47s9vexTVU|4U)$sm!^+h?bKzboNL`X}W?2x~D!bI~i0WV-u9CgtAcvZa!!ro^JOb&&=XQu9aA2q8^6_y-~j63_LRvcCCumTzK| zurzBQWbX*^jBzm`pB3`Jgy4q~u0*c7gOs8vPGhEZY% zEQUV*Kn_{aLOdbYgplDFZ50wz(DA&T&zvGmLQZ>>n-cQM$G84w+f;umC8WsF!}E4q za4RC>b+Sadqho$f$mbZT7jmLO7?GP3ML{h+No$I2SP+vQ^7t*rP)*8s3UU31-}9t^ zD8nvf$BZ*Z8usK1c?D-WnbeXl7u_r&Wuvr|Z0Q&&5hD8ye4J%vx2@%u3KIpo_NWYPT4*oPpge@(3&5rjSv#eQTVjxhB1E!i}G*QGBQFW$?^Wk zJlW`<=5{JhEfNwrPh6e$BZ8H8BhXx(OQ4^Dy?(rlsUDx`mR%JvEAz488hF`@&{!MC(- z{MK8pFrKRKwTclY*@#>*AS5m^-Z@?EL!Bo=YWb+=PDrbR-}*Fn>Sq!%V`eELN1B0u z=lH_dM!u>xqADzBnM1dl%x7b428nk9FfavR-KCN`#M5muS)Irdl>3$2aBcg+%!oty_`F zIni1#GDsJmoswf#Gwoc-tvFS5#Ar+T+jI^}OqfOsvSe8lygK=KBc25d9;ky6vl0x` zC}CxITcK`aA*+7q|CFd1p!|=#)+BR~^tL?O=&^rCL{25fdy7^3sBTb5H{1Z&GO}3yN2t2i8{)lnOnM)lroD@O_RV(Fp%W+{JJrQT(W0+B~mZWhVvi+;VOl{ms=>5F=9M6{=< z8-?hW(CS2r!_fz{oEx%)1mcajbS%Q*Sl=65J~dOvsF0X)O}ibTpN$eJ0wRysu ztm=VOw(!G2EkEA+G5>*up@_Uv(J$nVKDxyW2)uJT@`jbafmDv!*`L>hPm_No;;2vc zFJo1|fS>@!H?S6CJrL<_qrV*%IZlTavSQ>sB0Cq1lgl}+7o*1(9IWUsnA9mpJ>;}S zZ5kpvqL`Hr9s%S=joeEc#GN5T+8K4D)!V;@D_Wm5j09WDKk6KWILF&GIN<#XXf*a#&J$>nE4M(_^fipuOZz6;ZHMjOdy&xQJSgPvb6pU&uH_j$8(mqbJJN8OipDdq#*2Zq;4Xj2Cg%vCos|z}2<_Zdu##Cw$*hBe28lX{r8J{_2cLG`I(5kf zQn~gkkj3wIM?Ey_LdeTFlO|g6@+3auK>Ejb;YE)Pe(95qgt1N`#+Byw+y?cQ2ht8k z^X%<3F$yZA7mR||B(fCDrD(c^WW%*U`9HS53?_|#K0bWT9G-5x1^RHeKw3p zlF`|zWv7c;rk0nGDJ76RP`Xr0*?8w<3Jr{T2anA`AF$R`5_*&}h~3ctskJorX=|3O zbrd($+cg3uhkvKGjdf3Tc!UssARRLtQRrUK-VxFrDrZ&)Q-XO}xi_O*~^nER-6TE@siolD*`uFgDlqR&*LJ&RA( zlFq=$LS28ZE#<#CDP!haNJG{-32`eG>mciHnXBpQ_CBztr0yZ7d z5pT$+$aiKrdcIO=E`mw87KTMB8p zqrD>oCE~h>tdfYQBoGNQe%gxG21Gu??)E@@py%3>t`~bNM2)J zM!$cd)xj?<);OZsitIGB#e{T2OI3oZW~_~9F|iHnP+IWc@Ge+N3nNTYOV!XX5z;<9 zRn2-^cuSg1p4%tniw*`vZog=UXax$+`V-RHB%WX)@~Tmrwc7I_zCmj`2orN3O$)~n zXCe8a*VgopJJDpUH)LlG#6P}SZS)U2LbQJZKPoL+lS;>$g+SO+ZBbiWa^k9^SEZ5~sP@n-c;9yh*QkDMN^XupLcv~9>U zp}9#y8Z%!C@ycCgZOq6|G2)YW!-YgG0sqbVktJ;sq7|!6+LVvZYcPu~mIpzU!ZLqk zc4reveEG?%j3XFBOrK7--VogW3Od-n`#-7?vWz&R?V4us#J83~? z_b{^Ew&#VmW!t?L%&fK@6YLq0)r+1{+fRlkznkn-K8atLzIfg~m%oi`@O58U1>e3| z7FGkbZ?(hs=UcsFY_NT|E$pOeC-HwYAt60Tz!8zoLf^Ta74(IO>v&$gX zIw3C|w7gqziJ~XbQ&czT1C8ksMJ2;#Ta(hT&fg=E9nlSij7W;i^iE#Zmx^2)|YqX%0W>XH~D z%`tqJ|JBhGciCykwHJsCUq*krIkFSUhL#6}XGb;w*G5PyM8!QJ@?Yv56!h)?UeAM}h|hXAL?3+bt$9Q8g5PIeTsjG2D~ynm8oxTiAs z#=-p=mK}AY5OdU7KDs@$<9&>NQP=y}nKiADm|4w8ceKfU9NTCG2=NcSDWAdKgzGj_ zFPP!*>0Z!Bm#dpE-=8^cAY+cpZFQ`731nk*-={29^yhqj?iG8y`aQ2!I z*;SbF4CJE4C8Q0E4f@`xV%3q5#t}gOl8ixo>(4rOC`-K6AFK0Ct7t3x_en708hke> zs|h*LFM~UGf(#zl;N<=xqme{!XXYe7Ve|3RVmd3zwRP zb#3*<=!%ehF&f^3FciCddfsvwXt#U*>cPlDj0za&RSWTg{!foCuthu9%aVPI-&0l{ zsORJ?WX@J}lM#Q7S;a1M6ek@Ck%59yHrbqo`O+d3etA#rw*uT)@qVgN`;>ckvB*?V+ zZ=`=5&zpV-rvwX$fG@6FUzvFtdp~KSO&-YRo$L1p=VBkJRIH5aBWIi`7>Il${GCzC zhh3RnV(*~FkjGZo3l$=(23IufYm6FpS;PaYH~YBq(NSZJ+d50wne1S#d!MnbxCXO! zW;y6Fo(X^JRQrl?HTVXrXacpzPR{8Ke2QCtrvT3GUC9L&th|LqX!itGAH%V(}y*h{kuUe#Ymgj^U%N7xLbc;NXKa?F(BS2R?he1xnP$is?fW%?}n*qOlAS%LVf#{kDu;@PSmWvKG<~M%hA)GJ|lxy-&CKUjlCN}225M(RB3KoM}9!iQo1Y=yIM3{|#XB*B(7ot7I?we6gKgh~C zVy-U6cZS74KawHCsvV5skEoKtj>KUue;a?#j?HXX4K!@Y0l{4`sSLE%M>Ij9d>eLb zp$8_!IqETDvM@%ahP>d3w&kcnR5v@YKvl7M1nn+-(Y2&Ruf3Q7+o1U)Lexq zq%-sKsIPDIPe;o^G>1sZ8)c%9^3Z}D&06JmlT+|8mL~6~8fM`}lmlUQY(&8e-=BXr zd}D3IXz~HEmTk0aiPyS4?`RDU&x!Lg!=u<7ztr20`h(%CU-XRPYnYE~_-)L(C4EsF z#x~?)NY#?w79talk*c-kr;W-yB|}!#;ak$TGuP4R@|duuWb|L0BV#1**jGJ5%^<_f z7~wMDsTwmKjBE>Ooo~sI_F>sOb3K39$3JF~=Zbqef9_)MnA~muFrGMemqW*Y)1`uOfA*A?;lEiav9+{p2s4j zX0(;Zmx^I-SBNo-av(pffbWm!01=rxYz=-RZ`PXMGMvgLv5{EWL&VLd1B5a)k|3fXyMcdn4qS;F2OeGiD7^uw2VB{75Jc$ORX&k5-~ zQE9|*R=!zpC60NIcj`t){_$?De7E0ou3VfkYzZa#OwB&5^_Vf3=f~4_GUOT;QrL!_ z=_{Q14!xAIzDkJ10m+c5i@@`yX3owvKQk(?u=PFmm|a<=Bj6%En&o2_PxX)6J1olx*pKTyJ#OyMW&|{gpJ%iVJ#NY^ggJBWLK|}`; zvq^JwyXep9`wP!~nKG;r#vG;;P)CNQIkRWJB{Nu*8S&g$m&|`|%zP;%+J+EW-Z0xY z=ktu!!gJ-;(W;qiy0OOCsZ_oTkx9XPmXP{k7H)3sHrf3UopAS8iiY$mdTrbvS?de!(HYh{R{G5|5_3#X+2kLu!FitemfY%t zPsknBF$*_k5j*BW=Y5vg;(AHDjk_TA!?pV}=&v!qEjcB%$xnN|V<*j@uZxz$O4&Z_ za9J^E6&Zk5@_MiqbfuLhdQ(Co&%#K(7$cVDQVMo%2{C_WBUw=^h0=fJnwae(#<6T( z19>GEnGsgj`9P~iNauRkl}8ZPAaZ`h`Ib!ejuy%)rFM*UE_uNj{rXjgY$5)g;R|iC zRaR{%6IWP+yav*lnO8KJPBEh~vnOim4LI%23 zOTNfuAfJCaS3aY4<&5<%LZaaqk=-4=sTI6D=-02vB47oCekBi#ehKk|-tKBqa)ZC) z>4#Jfd(2ZOSH%5MXD$CVZBR&c zv~HJt-G(u))uT=n&uAY%I1^T%ePh%;>e(Om|LA|@#>6&6ZZ25;yhhQU;@^zYoMyxe ziDnvHgCV06Yl7G4nWugQ@}U*IWRownUDk*(Ot^FNZ~g%yreno_6D9KaJ94xxa*3`P z!#Z(Kr!?@7r+vm1l50x(W2Sh`YaB6KQo@`^UrQDw_VTP1qhs+6{^I`pH&fDze@FL` z@7#Ynk$p!bN?~vpb|?&dw_OTPfcQhu%Mnr!_9m?zj1Ksvd!&vL+O?%iSO-h+n&S!C zw;FoyQRG(nTz|EE7+V+8c)sTPS}S}PzWIX(Fpz~F*gA4TSv#K^!>AZ(7i+24nG!pA zLawd?Kka(m$SksMFCB9dLfVHFUi!+=sf>R#$2tcgzwXby-C6&<+?Z!!Y0)bBfI|9W zyXsZRH@tJN*~3indb>=lYS}TK9Bo)3otXr7dX?vvZ+U=*w`Aw)1vS&o(G5Ynz4Jf1 z&g06F7zUy#(StnF<{y#@I{%)%Z9PfdG(f@kdQd8_crs76Unz%#K5t$6x=>QAR8xOK z{j(&gIqzj(J+U%jm zSk=2V^Jh-sehxmZdLnxgV(h73^|gP@>*h5N80}mC)I7Y~%{$SOgk9pw59R3cTocUD z7gNs#?Z(x{2j5w3C$7qL>NTqOJQwU1uPTy6Nke!IH7V4;X1jnmwFwys!{TZzZpxh z^Djh3x88M22ap|NZCHD`y{HWHh_~k#qvYG#QTB%K;1Y>eXNQ03t-cZ29+yDm zwuo80LM$K?+R(YfAQf{azBJL;)4NvP4aoyM*P@&q zvmLj^GBG<{ccd)j6hdmo85Cl}#`E;H)jH5#*k00`@a%|daF?oWFL3SU?j>_K)7@K2 zf7tzV_aXVh-!?S{i;JQtH7iNX+dA#Zlkz09s$ezy%@?KeNxnlHXFHej^X}PlO zIb6{->PFw~UN>~~^9iYjp1CcO)Uj?#e@Nqb1zR>o!JcG$$F#BHT1eq8i9R}NqnB;# zqJGG0U6Y4i(CvBN=r!DX748?XWiJFi2d9;ab3FGMR+%}Rl{Wf!ggC81Pt$v!V4Nb`rSHkB~ai#@N>?&%)Zb*Nj|w-z6r*pIKIUx9=8R$2E8%aLjGGf~SRc=( z8;ntr6GOOYmmKM`i+bTiACH937F#sry~q1}qfh9>;d4$AnT@sCi92-c1tR1l;(0vR z11kw9!zVa0*YM|ms{?52R3i$aWj zX-Ayg&{OY;m;-H@)1BP%XHqGAs(OwSX9k@<0{1i*k{)ZOKI_DY=jl5QmOFiyfnA+W z_s}drCWnWu0J%v-c4%$yfeFJ`Bo72Co({JqCcpKM@oKr6( zMZQ^UwaBZpeB2ZNj_55$IL_|r;EcDkWrbqD8X+aJ&JyX{*oR3-3uqO{_-K>)sEA0w zEYTyP%DMaJH1ST{1*sXXhB#7ki}~pBiRS!l*SPEbIZv7a&-IPcbGQaK`R*a&L$*=V z-YOx+pV^5gw4Zwa;4LGK0g?r?RnpO{vDHX z_<_hO;P-KDhCBCe;|aM}wpqj*d_#^8bBq48tll*kj;^(*}B+Z+J0c z=C6<(@z!7M!Nqu^j%d7C5$}h$qtEU}pD)IrXbm?C&U_hi=Fa!l*n$*oD(cL6?rD--Ln|>vc7wJT?!&ME+PcPO>YYYJ+wmVLyP5Jy(df9yDCp5xLWLkIa&9Ljk4Z;F&XBr2x$j- z{C%Y?fAqQwsS9)S?yGTPhL;Q@eH)&tXOX8y+_%bQ3Hxr@s83&zKtg%oaeS`adSQl( zKFAkVS?H^IVSB0tBG($8c=7IFSdZ~yP76CU38@7mcQ1<8;OfYrEZjls#qJX~5E-_x zuKFb(UW;q((^$|yD#XVeAY!H9U3f`C1bsp;e}~MIcwcrcgS+5)PyE!fzY?Cr=k=_- zZl0Tsn(39`-BJ*7KLtsbS8|FN8Pdfn2P-Tc;ryLfEDrQe3h|yBh@3o7%e><4r%4{X zA`2b4%d5>tmLej6&wP^Y7H}CUYG5`XSBD%y}4iCk>{g7aoCJ8N=FvopDvk%UbZ)~eVpD= zbg}T+v;YM;`5T^ZNjg}Fw@xBtiYjCFmmNa^2!DC-5D`lOBc5;0y3m91L;))D-nXQL z`6RBWkKh`t`r*M4QY+>jzyFkAJRxg(!wp7^`^dZVo_-y-p^yd?jIFn44N9-5_d#AL6Syb<=ma&Va2v@t9P4~PO0dw8oZE2#t@NTz- zIe!aJrqRVd^-typ_q2HT1ny2G#5Vc{-@T#o&2mDD8Q@P!m?J|^YKE(gpN`_i47~SV z#r`2Bgb)|STpdY?zO8sGokMY%;!fpGo-83FRq~VwnfA!nDP-qFIkaBdQL!4Ygjh-= z=1}}S7qRscQk1a7Z%Zi^s&5qHU&!CqT7RS*bRk~BhwGMBSfqc1ypSntLR`>`fbmUp zPLt%QkYbooC`5(?olzlDfuzm6S4`ey%M_14Rm!vbBZsObOVF-j=9MFLQ3jC^Sviye zE~NNKN<@}N5)j!nP|dfHlW-=0_x+ELJeoq>&cP?BCtYGFj_6#2L@J@h7)c%oDSw7@ zMTB(eU|i8QGS;vQIffl2gvg#EyTog!8`cL4sS@${Y-V01jmU|Sn2;lGf-zDP-(Zvd z!Jm<7hBly(E>(-W(0jwGG9fD`UW0y9AT3kEh&HK`Mu^N3>aruGwoc4jNGq2#laP@k z@f{(P`-ccQl2&*^hPQaeQ#D>QVt=Y7c#9@}YH_|s6QYFlG%B8*Ia{OJF(I2f8abkK zfU;K%a_|Z9W)tN7DnW!8 zD#hM~NSo1QAV^K19s(#t|8Y9zY@DTdXnOvrTzv z;u@(<%ApmK4d=B9@n&xHJAX0k8i~jMk%rPG>N4Q8K}Qny_!M%jo$YYJ)TDG@TgRFDy4`4+w2jWR79X$@_R7+-8u>sz!pJU7Xa zG|*;>DdmDzfj+?=8Lx5*IJdk=Oteg0gM=hQYgb6g*gM0KS|}ziWPkcB0Ytow7@h;~ z%}Tx}AxDA_SEnS~e5y)G5%q$QiWahvS{~Y`u~^R3FF?pl6}d|+J8sI~aYP4%pZZy* ztUd4blp9(FvDR2AL&cGrXhytG5R+|&r?=_pQ12;p z_t3{RXosgk7HRXED1R69k2K^v(Q+A-vmm5-E({_*&jD|icz32c2_Y+F@;y1CQ^mh? zV?&)4yL3I;jqzXXg?FxdDIBg&?-hF;3GuGO{B1K&>Mkind&*L*Uq7UXh~ZOqMu|Su zoqB-?ah8T_EoymLij9#rpHXkCPLaqGXAt4QwVwJ2`8pj*1%G`gan4++FSR4>^P|3ZHX&MH{v~AIbO=A5K_5XQb@~~4=cplqBx+CA*qP#woWnDfU+boD(~f$ zmZ-9!#2wnl&Dzmh!y)3r9M^5PmXO`Xy&}%F65>V0cuz(B8}IfJ6{23rLP{~}c_qX$ zcB2>4%cWlZj(=!H`3bxuF@M{%&L{CKp7S8&1e}d0d#Fom5X z?zRwdW&BL^5Ez|AnI%>!LsE!L2|NcWmbQ>_rDy?rp9xtBGguwT8)F|y24JD8m?8}v zur5?c4Sz=2bj1h6FSH6$T4hppdXb(D4P~N`y77#roHKS_7E;|Ez(`7z8!n`$Ox3JH zWX+*xPS*B^-Iz)#i>iqnsf!}GLSzD>zc&?k7U8p%o*&B5RBpyfbs?`@J_j{4<8vUJ zDPlA!RV^vXw^aLwl_SI|<*5pB{{cV2lO>ERT7O?F=#9`pY#664o;wqGMYRGrBT-1t z2dz*c6}x2$+39^e+f5IB#HqddFgljJ*AYe{9cc~Ej#cSK{)|`}uYnz%1bl)Xg7KU7 zP#?1BB++}(HcKAM;iv9fhBZE0-}NH1OZ}9L%pr}$6Jdm83#`qCBp-UE9C?(5=tv1v zJ%25vmw=vNAvV#okj4lf(q#-cxe-~ZQx21mS3CLwe2hV(3V$J`RkYF5bn6iBsSn8D z`!idcd_tzk2K8iiq}9lu=^;`Uh$Hb*2Q4A19_s2;(vo^+IqFltC{sy@hidWcES-3? zx4r!~W+4h$weW7QlMEw-Y2C6Y@2RAn(tlwMD64I-4B6fz7oTnSFwkxkQZwaj3mL}z zI_+I2`qtBa8S59krY$7C!7_slV@Bz;m?_6u$cTk?@Y(tBZeI};w8Yc(uAKO7ce(*j z$V(EJxYj~0Gh*2#7o&a2yQcDOe5E~%Z>OhEP!76~9SVB3h1h0!@NU~wpXG>Qjekwa z!ABE)AQkJ3gos;Wu2c?|u#3Nt;fxd!ugRxf5K^#Xf$Z5B3*PMt~~G)JMEq7b0SVR7dHEgcY70pG=BgHg^oB zjBD+EMv*7xf3G3jxqREDYNC=oCkh!#F11DK{4b>1b$xENZp`@+VhufJxwW#;hcCn?ueDdEQpZCft8I*%Wq&Qeq#7F` zFFe$MLU>+v$rkPM6Ij=s!*8?By-|f6duXF)yCG24xHmGW9r;1-rv=R0&?T*d>hww; zi6?70k`u;DeC#pyjSwE=>Jhmq zVU|IjFR`aS`|JUX4CS@rqkrxLLTxuu?Jr9Hn2E7UdT)hvmNuk38%7^eB3F+ zhLA57$-B7zathoxf}KwPn8>i*dJEN^GS__bbyWmO~vxW8Z?#Mwz zCO>+DC9GoOwf5&mU4O4XWA;?sVTQc76ul?)v=H*rM8CgV&IhA?r6%vQhDb4-^(SP6 zjJ!%nyO>p3>TDBj+ESNI)U>7USTPq=ht0UUqck_*N%lG!#BL;xRF1+G?}>?#$kLXK zP`vf+)=J%pw57=cYUd(D=2B&ykRChLp9&duQ6EhYE&J48M1M|8d`5Mq21ezDlz~y~ z(!VmZg*2?IF5c{RP@j4s-8Rd_?zI)TIwOXR7NwBHF;^k2W4y79?5+P1@t`N}_Neu; z^w)A$v}q+?D^4~MGGoOkX&F0JWVdDLW@EguOi>rqfD$2LV*IDfo*HJt6lZqGyvvG) z(|d&^T~l1q<$r@W@l-7f*lpGkPdq|o#f28Kw}$xo%(H|(uCjepGoFy0>!u1zAvGG` z;9cz^>nwZ3!FPL)(pV<$VGCo(Wlx_mgNPm*Gu9j#SXWTCXC~lD+>hs*{gI6kn{vE_ z0r&K{yvDUY(pr2DB$6NNg(CsW8t|BeA0s_I%SBDsk%YXr z#+YzMuYZsfusejOK1&TVQsd(%knN81C>=4*rx3fY8NFXZ8fP;|_J@rupr(-m`T}aY z@dFXn;JV$Klb8d^5JwV5hHCctGPrZOMeK+y#L~eSWzEv)=(nsgfUxV9kSrZcx1@T4 z{>QgfsUKX?vKnXYRj+_T`$tS87W9i%|HEvPT7OT*$tLc(l|pC4lOt!J)h^|To_ZnaVVzzT z_kV8~6{#Z|XHj{ZA7d#tdcesuLWW(wr(@O+`8HPJ2$UCZvtPN6>#2*3v_m~qWNxR% zth3Jik&jfTjxT!Zh48dNAv5E=P$6zN;`>ameL=11BNEY5Uv)rm(vgrXs5_d4J%R1h0W-GoY`g?jbdY&*p=zFc-9L59Omg zI3Y5NuIHfX#R?p1F_t^ML&{vXx|vy z_0C!mtIC=)eDnm1q{WA)O2nThfPXvk5xHw=dG#}3UGf#4JM9+<9$EUTSb=V}`0`jVsPb!$TC&c@wGh$q6+r$M~zBTXjfd0`|cAJ=| z>a~)W&$U;xVnnUUd^hy(G*M#1Op8`K_e&G9Bwz5^>dF=S?=*@3g4OM<1%GjG{GHY_ zPA4zkn>An!duv;EHNKCK#&T5uozWBAWPL>BJI!RHqldPcCjj6sn8ZBBotyQ>Hb%4A zCf_+b@u8Qkd8~k@2?=?D^SoOZ@kY+4O%M%WqdpGQREacP=iR6O_(Ozh08+R*f zH~ZB>q(^Ag~D-uor9k5zbNWPeLWL|QBc9sx#p(AoXYJDwzMTWGa z(x-GwESGdJ_t<1EOn>SaD#YqQ%_Lb_B4)ixX$HyC7pKp_jns(0-!~nNNHa-{;>f2KUE1 zNn<9NNhl}Yxqr19_E#3NtzfmJt=9^l(e15T@O^gj8W%*gI+(j+qP=WUdYgyBa%cAv z&`WK5rHdA&keZNjnRw#H`g#*j9B372Lu~RgQ3|EXV2!HwLCih3s*0kHiA#ruCPlMzq zFi|)>RdTAT{A!c_FO~tQ72FhKJE* zAr(923xAP-L+^19Po#@>p~nyl`hqf?@2i$C)0#Pdu@s zmq3V*SwQ54fKkQn9S_l_719!V4SR}tVTQi6Qe9Yg*prN1^mX?153w_1?3wFH|1e(C z(`^fHy=&z{JIPhCQ&$bUzB$z`FY71B0Z@7?WctorM2Ibwcq z_j850?%jKP@y#kbFW#SWd=3*LPc}Nq#9m`)G|9~qj6HXQk;Xl4dd)4NOzd@(ht^cD zJLBv^AzzlOkj5R2dMjlk=j*M<%o3?-b&!(_Nf)zgyXD5*Qix67ADKp-pW0S?yxX>< zYJc%&IkiJh-tB}%GwU3YWq`M_$JVjtq5CK-j63(<+d}VT?<;NeF!g>sF&Pm_QGT`& zYQZQ^A1M~JPy48uJO4YfG2=#v6|`9TxaFcn*=I-%vkdx7VPF=oka}64&Ri~bBJ5K} z!N<4BY{ck`kk+v}x6kfrbBGKnM_hxs>wiEGlMpQdp2X><1pm&l%ra!9cK9SN*+m!` z>Z^H33z6Yj9f-_udEM5G8)HU7d^9Q|;`bP>5aM$I2=UH}$glfOaif2<@6xz#s_$BG z61)&edGwF=E%wG7X`fawTdVKqO%E9nQAFg_$T`76tg&V~Vj>vj>1Tb!nflo>#DDpB z+((QndR`fP;%|tDnca<2=yTE$m=D|U_9oAWoYXM--(N_DS=tjJMZ3N~FSp`v^W+4Suy56ak^JGUsAxK$5bsby z-!?)?9d*g@UYTftjj&`f(3_5M${$4E`Uo<(v0u%Iv0ki!5@H$i6Gu$$gF{3-?c+0} zahI4_0Um#dU6dgs$#}-Rw2_0(0={XMfr@8mYL$n>ammBC^97Y*-8ux<1)kR zW-(uEc$pD$Wp_nS^r;FFRb@o>Hfr?YoyM{9USxl;9Q3vhufaujJF0AV)IUQ&;FDOV zWwa-0uD;bTu_qIZnf56hx1_@>@*k?*PL39uT^k%1n~(PQE0<&g1{ zv6gi7k_w;eUbNpRDMS=;@}>}xBxDXkjHhjk5kDqT4ISUbYduz;eCNi?89mNpMk^GZ zg+o(+8^wPHQc8C^Qb1Z7hK!mbAl+Tk-Eoy1FiHezl95;lEsVr5yDl+KlNmLma*I?w8DwXd!b zmr|t!dV_wp%x9UV9rGzAzJgg@cvWHii>~bu-G>1n^FUS!Gz3x``-AE9x%$o+*^OM!_FIL!n z!rSw4SEpbUr2QopXiWqDvf0bS@&r5Djok8N6syLSA9iaxHA=#W=e1@L*8A>-(h(<9sHDz%9z820Hv6p_70y~Q zoHqU^po{-rhVL(&31jXnzurpWFMgE%2pC8Q+hRMS=RRrJfvA@(A9~ddi%&I57Pa2|Zc}pKRg}3!EIE%QYV92H`=}iKI zaJJn()cFtoOFpj&BK)?S?mfbl%#quzYDcP~-7;VNZrC7B#3~ITGCeA=>Uf^tQ@aFm z`8&U`m@SF5~Qr#p*w8O{`hqH$_aVd0Pn})LBiB?To1&}~sWd^(8ajy5bH0=KQ z+2F}*K=wnWD*rI9_6N`B{D1u4c}W@7FurnsJiB>4m04yt367=of`rAAojR01*mMog zDCE>J(M~t*rWkKXEi_fO z7?$3R(8;u%Qb3^6FLL2wIkKDtI$0>$4PxZ${grmgP4z zj+(1{2JqK+iaqy}xa+#g#Vj?-z^*b=yI7v%hW?w88r#Lk6=;)qK>zCfxN`H`b@@*K ztptT_jpdix(n|hoU)}Y-tS$kkXQUGO3QSkOjqFMZ82FHz!=x8!IEA>X<(?yC`3 zMAJTzd8Y<5+;|t><;0Rr%D9Cz28OIaZba*_d({OGkjlz+X62V8*ovxj8!aPeNX16A zQzqYIU|BCnc!sZXU+9%pr~b}2PbY4)?Gy!nAzfB~`S0`?wn}YD%gO6kHlQPv3rm*R zulr*l5;B1==;c_FB(Pd)_-y$tXo1o<+?zhY=5)Wk`B7=;t!IWCk86%6$2~saR_i}S z%OK)jW8v|p`{|-w{W)0%JII6g-a9M~5B;ESgi(ElHtsObv|GVO4Q5n?D14cZlg_1D zs~1zG>sqsQI(MPbGzb6HNaVrl1LP0!t5VXLielp(V*R}*J-d98f&wS?wU=pafI zm9rwXQ97JUmY^)Zc{-_{;Rs-?nqG~yzL&g$#bTQ^h+0zZf*+Yx|MPFQG8D?SW5383 zAgtOV(T1JvG@q?T@nLp&dbDjFDeD+_k)^mKf}I{L*yLp~msDMWs3TbCYv!sX(I53` ziDzLD5=xV))kEU$zwsOpG2UEMBHbtXuQ7>Pe zmse@ai0z=d*`x7+Pn5R>my7<>p?7Y+7#Ny(#{r0(pRsYOe;h7!lW9g@EOau=yF$Qc zC)Zx~CTSih0y@iZoDTrKm{5==iKj`=poGG8iC`?_AshCRisQ@bnnvV%m!&sPA&uoA&`h)9pRyomiTVOGa3t&RQYzy|u{r(T(McUE4 z-s7k{i_Z68o|g!s>~GBLk1^^lQ&;lWs@xom*gR)U=ku)yi`Mk=nD#%9EaoP!nqoy~ zLl-zwLLHAne%wB9qk1NJnc9=UbI(aAt6$H`5$ZieoVE0nQN+paBzP2WOQA+;P1>CS z#B7qb2D{c2ZwDak+eONs!mZ@{Qf;3UUn$wGt{DI=6g%tKJ{c`9PPe`yav(U&WA zxFs3)KtLHLL1W zdd>OFM2Wp9Xyh}>h}iy7v&y4E_x*ftvX`Zi`qCMJ&U2*8=&n%tg%~BEVga+cF8a>Ts@{i3&TnH+ zV6dN_@V3)R%FX|`Qr|3BaI|fyg0RWYx>(K6n};`~NRNw+F>iWgm*D}QM1F3kpFDj8 zMVN%p9$NgIH}Gv0p@yOo&QR!zCc2Y_L~kNwqlCTX6VJPg_cEGX6w2b)8=(R<+pRso z-pY2uotIJD9dFoU%TX31T)QhGnxid2Lm7>OpuO@Q#HY-OC94!%i|{@lbO2c!Gwnwh zo|9}-m&n2;7J2kL$m4WaH8uJfdcxAb2pxPL=~y~(!x%4YPVNb=;BjXw@zE-nR*NoX2|)uj{-BaV&NQsLdZ-H z#YXKSWt(>x*V*7&L!%3$thm*mH4-vmA>iP~v&ivfa{U9uZhs-1;OvK-H zj(4w)viS*;1ywNDuO^9(gMK$ZPbj7NK=If#H&H_7+N*@k=&CimD@*_ALqoRD7ToZh zjiC>G^qoz!vwn84*34~F>F4|kU{(EjB(wcX?N19EqfIU+_SrrB_1Hyq;frNKe0Cj4WuXMT(h*t5RnR&JSJ z!aUux{0Hx~^L1UQD6?lK(*4bK7s1R`glsiccCq(N}Li31yi_t2HOsneL^2~jVGL1$X58uv-$B` zcWS_em$%zL_z7kv_luv_)>TCBw^|$^mOb|^?yn3Nwf9d0W1eRpd$)HcUf7`$xZ^}x zoFucJL*Q0yEi32X9dm5pw16Kp+Uq#p=fm3@c6o}PYergk`aI`A%K1O6h;L&tGMdgz z}8@_xljtwe(P4C}0P|gZ8pI7}gyBv(CZYTVs zwI=U=7*FhTYUv~U_vzjscIBV@xr7y!@b$(y!mTGEUjD)idc%~G?Paw0E`C#L-C#!N zdEt?swiL-cd+}q}ZmvVEW_+vn=0HJri_f@>(%(IWPz_NCTAKTs<2CF=mJ8IVlT=DgoNbA2l?V#itA!5~+wlPNYQDVx5l*cXS+p_LLXPcwHyK1#4prE4?%6G6}6DZK|hJ< z!g#J2$Yt=TG4|Dz6i(rS_gUU%g!50KFdAP!-1Cj-)6wmr2yN8$hN8uV_L9wEmi+Bh z%{LK5z2-f{0k#7|>ndtLv4D%aCNJOBdwJx0jp6!8IK$rzgFO??YFiPCYG2=h z)L*u_E+Bqwwce?v6=!)j?n8&@q-PUzfM9uH@7ox^72(fnAE4HQS(}QdB-^b2wyO>k zVpNj!mh5rwyOt$POy%A~adyUW*u)bQp%$F!*ARqX9h%PmPc^PJe3Iojiei?orBn=8 zIg#Lq)EnHGD2||lywZMro0FhG^8s2O#@-i<1m38YW+59CA8xzj zfY4JJkZb@o(8Dl1?bapn|GGL7NrI{~7JMDp^tY8Iwo3JWC#=H0H`6R#B_kVFpsJ0o z&%tiffshU4HYVC!i6vHeR2Rzb7`^wIrcx#un)!-?YmpD~HmfI!(Hiix+?&0@wlw$= zPf*p7wbL4#&Ry9~wPF2YX?lu(?~5i-9yFWtoY_3@1FslJOAg+)tqa0$49(llYpvVU zNtS&n3$+bf3x~+@78zgm{H$ZMd9%TsmbMB-*cr_6w*=9ea3O$9k)YmL zvT1$>38I>Tic zx$U<9IHGO<;>L*BntV-^4iswk6WR;wr<-|y&M%F%Ot4`L-g!7t^wpftQB##*&cr;b zr+(#su_s@t_O`T%(0;sF~4jXq7nHb-Bo6mn?fYN!M)N% zii>&ceqmCC^vqHQ3Omq1sLXIrQ^m$?S)z`37JZKD=c-s2?L7;9Z06w|Ystk5dW3*A zg7or5G3&jctaGqirw$w<$9IvOHAHa=j`DB|JV~G9z_d>wC=X}I>AN|n+WD^YL(I)U zepEq!f2zA;^yr-d>c^^67oF^v5OJ}0zdw^B`~uANn-uhbeE8Q`=1lDA(gDoeFDg-t zJs)a~B%g0rVpU$eQD8UUQQdA6)csu~0n+l6_*+Za5%wy1?gTlu1{Yabq_9eijBwPG z`C?MSGEC+aT@#zZ;nO@y$|f0rviVuG8=R&J<;?5JZH~(-g>a!zL}xg|s|BN*TqD_V zv051sGSCo^NfTF^yM_AFrs5vYnEQG;{9Z@ilKhf6hw)dthq)U@yuz zT_>DSf>B#^Nm!P^dq66!dacX59J4Hp-GrtxtTN??el~69p9G@!<8UzH5^x47}8luLTi`yz#OBe-8Eh!Jz?Ryx3PLu!r% zqS?9YCs1oVv;2l>Hs`Mzh#9}Ak7&#vN(wN@zo5=+TAUf}+2`p|qEwdG{e7^BU#Nb` zo+jgEj_hAsAqVpy$QcCS;w%nwoArzM0vJ+6 zL5l~byQ){>4dXiVYR6?V1rI5l(Z}NHuLm{q)1~u$6hItq;C8*GAE!pmuQ;|t#FNRz z^I2ummN;bSa3_&irxt%$PZDk6BYD+sugZrw#=#N=q%W+b)7 zU$OBnH;U%@@%MeDXloR6WkSrOTclqEH4apZ7lvv=f9ieZxC<5Yg$+*e#>7b@NgeqY zlk0fnGrJEyV~%JrKGoB9e&=m}5g`7OW~2#=w+t7lf=;nY5LB1o^LIl^j9bC-1K7tz zpt^#)L5tFhs=kQL2J`jX>$i(F4O62Uk6w!oU1(l#hJDS3NcYsK-L|?U@K#od)3r_+ zy55wMCbx#HBd7%H@4vhBa{Czk1LmO)!x*eIzg11Jk)rK;VjFN#)GfI4Zp&?~6X~H; zZt~A#XihU;T|b!VXR8d;bf9Orp;Y(q(H;MxOLJ^UA{@CS|BNve z7g+5|YhBRzMn((!&UNkx2oM2{&27bRB74=Y(bA@|<${OktxPC>TQ3JY6tqZcdY2uFRJd- z5e;~L?5g`sG!XkGN0`|>r~ev7<-zNlENe|BR~Leu?Ov|#mVwU8K(Xm<5%TLW%cbH~ zKR3k4m$>WsQ(@||oezfg(a_2szYFz6of}{Ay4D+Cw}#PY17-WshEj>#)w6e*wCt9e zAztm0485ZBUFTjOvr%|>G`IxcUsiv(#hdp-Axv@of#e5WISWD%neYnjY$e?`u%sKN(+#Y8(yasmvE12%3Ycun~O+{A4b9>p6s5f(JGx6MAA^%Yp z=9+CacWF5%7FH8*r@haa3KIA#d(UV_n=}1Yzl8aXHN{mXPVy*E)#oubdwNtd0flQWYCp-veB%}3 z3&yYrNyaiM(Wq!9b&hdoT<{)g90lE(45-qS)|s;Q_fwn8)$xM9w_QX^Rb}AkbtkWc zfo*xtZ}=}IC~nzYFRC};1e~H`PZ%1C# zD>8Q0YJ=PvX!v@}Lb|!PR!fUWb8(wC1O5dnEDF?V2s!_&WAr|K<)tSTr7wSAa;Vj4 zny!sbfPAjbZ~8OPZTEek_|sTE!FRPk4Q;V=yr>J}mw)Iw)~drRvitRXR5(5%?VtF{ zw$$P!I28-k*_+3IY^h_V$B|LRovZ(`ANcpA4d^6gw)m`1sa0YAI9X55*0 zWt+-#r8N9y{lZuI@9vOOf6~+F;6?z&<%ksNw+{c6IRq9m|)~a=kWzFT^8cznd93l>yx-@XiK`ArO+k_=qzetF!YjYdxg6yj?m+jwQg^$V%aVXAt%wLU zNXsbcG9>lZbRxTdnsWy<%`z~cyeQ4FV>Xc??id37kkFQ2KQ&@i8`(QWq*gETp?JV+>%Q}f@t zD>^PhHg?E*mKo`@O*XS)uXxyVafRszE7iF@K?o&KnJ;|XZCUU$4}W8KiOMQ z=Psig+`C) zO0#mzG)+bsM@$Zrz45}{hcp7;FL7^!Yfh$W2&6l8@m0w6(65ho6I~>#a~(Kljq#^` z54E#STNMM@^+8k1fN71at}jFefz}^mmi}N6BMZDP#p*c^l}RCqrbxxc+_#1)o&SbX zUAh{YJ6)78vVU!byqR&eYP=(1l96E_In$Xc4cCwdua^G)2(!pkQp)ez2+CS(W^`YR zGGT`0tZ;Mka>6mR;}iyLUo-sR@vvpk2wR3wS^y8V$znagK}m@d&pksIdr$G8w4^qM zV;OtoC1Q!U=Jt8BC0<*|a)sUlUHNo}E{!Wd?*AS{T6>7F6&AmUMYMWlYmk{fX>K37 z#?+Kd-ETIo4e$FoN3kg6DnjCy4-qZg#~sb=yr1y%A#>3lU!KcBm+}PMG zWeq$er%D1!6^Mrl4mGY7`LM`Z5_UQBl0Fs_azrEd`Drq>;M9?xFE4|h5dMVO;BeD0 z8=83O=%<}-@;DhPZfMR*Q-W7xf?@Sk{bye$vc+DXo14MhMyk?s*z!u8nE|o(Lj=UQ zo`Ws2mXX-rR@X8TsGVOn$j6gqJ%FjYwltf|bO0QpiLUX7J5!uN26J0MPP23elkjC! z0H^VEBQ5=OD49DLp{cp#vZ}fWusSG(wTzEQi#P|gcUO4i0NfTQbWU;#} z4Jx*P6l@L01Mh#yqDLvI$f2iO5WNHDUr!Ap`9GC#+ANjD5VB1F;t_XkGgbea%jZXK z7R1>&^9(Fi)xcB7#e`o2;%t1wUlx6^1l)Vo(azixj0w85CYGTJ|GjS*FYJqN*iorF zSggE}5Fzh8a2coqQ4|w?CtLc;>1&I3RAlFa4R=$~Sc8_mC*c*EJnLsYS1UL5+dAVC zHu+VTWfXCX0dhAX$=}suzp|?38*6VZ)XexKo?)zI(!F=F)fA)sWNAZya_YysV_+Fw z2slZ}r#W8R)GsSI)AEem);Z-kmX`E!J-~^qmeE zE+QPrFA%&jQCVbUwKT-Qz^S}6&9H&J?pJy{;k4kWIEb+hh1eNC%ypZHEnPNI@XL?r z(Q}^LPZ|9QPl{HbVzTW#Z*N!+1F*nnH-f4kcEoV47ie=W2FQKs=HA3z;-|!T_Cyy6 z%JGyGPJj4BFEp-geYyv?4`Qjo75Vl^1Zv`UqKD^Ke=X)$D@*eJqIOWaqLha2{U{VX ze*Py-CNJ7Td@5fE(f zGrGzvU4xDvSv}C>1r-BahG$yS^%@;NLOBKvhFvwxA>wA8*SkYiTQ>@qU&0%!Vxt-j zKn$J_61vKD{V0VjCwI5UcO`gk8^#*izPtuY)4Kmf6V+_5X?jr^aYX9WT$>pmYx@?r z#^nDszd7FeTDy9P(5w{A4kHlU)4C+U9j0}aA*U+U!(S9wCZq(|%odEG0orZjoiEWB zB=%i}a#mP8To2=x|!~7_}Z2X9B2B#c+f6Hfo=E=NcT>0&{ngOAngjp%W$#P+lD<|P9 z-WX7q>lGYEu+ELB0_}`nX$7MB;b^K z;tl>0P3zZL-L9=7{Bf(%X>u}yWv@48%-`oVL-E_em$xPJk>0wqi7^iy1-W)Lrk%~I zBMebLZM)x4Uu7arR|HJ;tpr6Fq8^+!U`wD5@) z7^m{jZ3(aLTR&73T|Kk>``?qQ*(g~LeR<4S)^A)tpj z^CaD2ym7nD1F@bIYbJ6&51tqF2!_?an-Znk?uoGbziXf|+P0Bzy(ycjP0e`|sX4e! zY4YIHLZ&m|(^6>rV*2i|2OvG<@A^^E;blY2bdhK2G$cI-l6>yVU{zsG!AQ17>`1t~ zYHBX^_oI?F@9QqIy@x@k{&t#8IIqD$W-1p(%;`R*0uF^#rs?RD$G7r5y;dGRGq=L) z3G#hN{^5=xCNNwhORfCJcIBXXTyuN={;{!)98$h}TiNDm^R(q~7QlsG>mz|?=tHnQB(Raj-OeJGw+A_JqTnvh6>8iG@XiE zex!g;4$_@(e||6{-``-21o z&t+4@y~#OmB`x2@!E<We0k_#|xD_$Dco?Nt}(7Tnw5+(`9`fnQL(G?x% zB&^32k15mlOV0EQ=uhbmies5Fd-c3;??mF-W^qt-IU@BomEv!AO5?5DNAFilk%Z_* z;O{09-8dlUp#_{u=MD8}TeG~Tu4C`DNgUKm$3M(u0HSi-qg&_Z&aU=tZGJ7*H z=?BhuogciYG9bHMxvmIpEI7~BP|)YCI2$ZMwpHODt)tQiMpAWi!@i+cKSwR{0~K&~ zvEsoIU_`UDY8?XTA>|LJ+3R1GEb9p51bc>SLsxakhn16>OHyolo1!*-c(vJ@^;w=* z`z!ooojQJ7zy75-(U+bA(3=v!{sU8E|Hp8i0>gmw%QesU{&q1hz}lIrCqq-OkP)A73{TTBLbJR7xF!;W z=4f6XFcuR!f6(}AJGqVjn(whNy-wM9D0 zW?JtWyM|Ty^@zuI;+!^K!w&x7)Y`U;-8@I_>?y)FTs~`kgcWZxj&*eVFrm{lay~;D z0K@!^H^cOG*{0Czqnm_%SDe{Z!5>m~TZ2ZOh8O$Y;m%B;I`qX8>j_n{0fAq~ z#&s-~*xr*c$;r_m)u;c;-Vw}#&K4VDw5W+(s&IcWuEqD9`fm8YZB18>BE8q1qGUD} zH>a)qujHxDcFOP2{-#lm3BnIc{((>>APq%i8k4+d&vpimjZqox)%NU;ocsofFhO-T zffZo@ic4He2C7)Vw}+I*_g-Y=4*NI9hHD!+b*>pu11cT4gHK3hWe?OF*dMGcN|Ob6)h&7BKsFb@lM{>I(aGHWk+57CVVPK}`ncmllQr zMNj@|^s>pj2-cv9a~MRMPdkV>Zs-;}{VDfIXI?M1o4eLZhUNtic|@3)Oqk^_aM^V1 zB)-D7;&nKt>GYL?*@`f_eC$Lssr9(d%RX`o!hP%rL7L`-)i2ZV$-aB#-3_mWCzZ~~ zI%3ZDaIv{0%yYTC;jL?pYy_a9j8m1@EeJ|H^>6-M~56&hE(a(quMamq`fH z)~rh&vd;ZD#Ji5V(N=xPZQ{qB3Urumm7)D(V71wr#;H_2YDRd*d+w}v^5i8fNpTS2 zxF50|;t*$B_K|Gt#ggmU7sS|qaYT*%_Clq2$3a`W)$aA(&9#e*L4Js>VESQ+wfas?#N;77IyMi6DbPzsQBeo;J6<9(NC4pK4Cixg(Z>{bzId0|%d44aTm1|S7sT*|^xBd~VKPVeU>?0?S@AR4e zYQykFRaCr{lJ>3dS)!+m@Ad{GPM@-q>-z!Eh@WBT#onyMMNkYrPkV$1Weyo{mA~KJ zyR$B}Zcg4Nm%W;=3;!lkZ>Ge_h^}?9`!I?Ng8iH#05e|sH1_~=0)9;~pL_<}JM2+e zg{!-n(_Mae&DTf1-i#x8zraMuw!|K3`_8v?OS(=u8T_vjalN@lJba!hUJRBWlzl^* zca)W*1QcStj^)bl8Z?=EvXtuwiFuVBtjO&b+5VL#%4S^=x9wY?6J(yrL zeya%G+^vugPD^M-^;?`%l@PI<$&g2gqt%KkrllE*&)1H4`V;l~hE?ew_GzoIuGz|J z1@O$YheyGu4s11B4RO1Sg)XFJ01sDZ+SuOD9 zz=Y$f=`^R}9K9)Mc3Hxqbw$dHx0<=f6HIqM-)JJjx?^^M2d?%t@omtW#R|cZAK99| z>2==>zv|sgB_NFKo#~1CZK?jMiFfZutSr&kOVm{Yqe?~3L60G8YY4SGRGafHQ?*iG z6xUO!W-VlxN0a6+#lekb61DZo52rx3q;?pXw#C}}V%VO#Qc{YARoT*mS`nH|S6(u*T^+FoF@R{94Nut(h51px(CM3p?sR`-THZgTFhX z@ozC96rqOUvlZqo|Kb>|9&P7+cGYpvhDRfMj*wcq=>+dsZzI5=WDUW|aZ1~%TO@9VgE|c}jgv(Ihz1IzDtCa6da%kbd#9UOw4AR)2qiH$em}cpowf*k( zo}3&r_W#JyV{F1ictUyC{g4 zqi>ux#P++tv+L%Na~i?zBvpn3*+I&VLpRq(JKdm=d5Xs2IY_4Bk{-+Ti#JPqz zX+k;l3bB(Rcsoyfu=K;udTPGijtjN0ztl1b{a%rBH1xx3xG0uvCSr)@)qdY{$*l>- zXsYxSj+R4{MpwMRvV88CUOU6myrb;#jCl_8MxtZ*`R}nH0<)p9`l?6-pJ|WKFa5co zNr1)ilBa@fn{#o9LfSiHV0HXW-LS;1a$1_k543lIHq)5gSzFE~Iw_mPRA{Adj$eK0 z6!M~{FXTqsMn)+h$<~*ZQW?X)chVGjPw*Y@cz<2j;X#76;^U;4DVDY@bB;T_2~AB& zI7e-BWs~2PS$%MhS<)r5BwP|8e%s(HG%3)qg?VJz>P<p99&JkOiG(Ts45vVvuv|$sf<*JS87jS$(0&*DL zKMChYQ=A!sno^KfL{&sZo;MRS@H;NUX|p(OUEGXM3<7V-S*C*O(?aLu9IgwR%g zq@)I^d13=7yhx?QSYk09UeNHEQ;emz?vDh=-(5d$AAYP+y@$cc-uS#V5BWs+Ql4FD z34O{G&UXM&>h`OM&F4tZTuG1yD6hzD6w)tVgzR{#l2^pWQCU=m48o}HqIaU!s<9j0 zvT=31RTqqqtFxI9hYs)G(KT`jA0dWEZesUvBN!0)Q{{JGT)D=`Ki!EKP>%W_B{m@tWWMi+c%H&@ z2-Q>1+!26H!hT~lW%)|~y#6XH+JBAB}qzvm2!uKF>6}4vz z$}8K47sO%JZ|IZP5)Fi08D~r&e>!(@{72xux}IiJDRhY`w#I8G6Urs^cMvPRYBPFc zsJOyEi-?iU(=a66kBNnW*)zfj_x3X>2_My#N605yOgYU?k*Co!D@J?%^n(e?*72UO z{35n!rkw4|(`7k4_1tm&G}DtK#A@W^0u?n|q!&xyD}08t*g^nMLe@!9;(_ulw*0sw z2-cbdvVwlJlk(}S;Kj{4E&Yy-Vbeh~)y9ckbathXxKRu`Ly^mLvgRpak3v;?ph}gc zPcZx{OD4<-d7`yluut}=dn+3q_eLWFBZxN-nHerR@0BW>x$sSp$){UTb}!^&E-qeP zgpXL+37&5f1AL98=3k9yIYson#z^sF>9Wa2gYy`Fzi412-9Jd1zIB$_ zn9UJIZee(ZulYgT0&lTva2fc@lB}pm`iNZivv0nfFYPK;UgqHnV_XD3{Mb~1Qm%xal)g~J-X{F z@f!YKqClQbjt>}=NS_(fRO51kw>LaP%(hXdNcjfDZEyk3+E#sYy<~bGD+EsUqf9H9 zXbEvP8w3e*P$NcWERXz@5N4Q&!kLz`?`_*nfh3?rz3pma<_br$Qn?aKjxS0U#Z(MRZ!R2gUVTOfVj$H+Ir_l3p-_zVNdI04fr*pa7FA_qUi3U5-x6a zX+Sh^DV)>s5ZFEQjf8YPTaQgvKMMe?9qANvm zWHIt&wYt>dS@LIn(OJ9VDT`NGdCfIhkWzp^M>e8KZ0!y`A4l*y%In+X6S4~4lzCq7 z)&|okpZUGM{9GGO5Lr;gXwI6A)x=n9O4CQ($~|c>%n%ymc%eVasxQKAAocIvk}91B z3)X1DUz}AQ&Ifmsnm0g1 z5$h5r_k3QzH>msRP%6=wUmX^s{V)2rU35NgPcN%@TN%4PS1KakkUy5Z!Qn~vmeis% z+nD|H#vMn8I{_RIan8huo1?QdQvJdV7M<^7ZTY>aU{d!ju7pHW@5bZ%w7H6GSVJg> z*|<&qOsQJ55wfh{`DEJViw!7S!W<9alA;ii1{ooqt=Mi=T+Oezgsv-MH{J_G%a_HW<6|wR0L8y_$>2RhSDAO%(={zK zAC}uJDtE%B__VlRccd4{Hc%)G=#I!trlNntt3DR?B&VZX#u8`79ExEO$(kW`S=}{N z_H|t9Al{a~D{*-zvW~I9iDiQ+Enyeh-j@m~gZ_6Ed-YLPA!Dfb-C7|zt5uBT{Lq1h zmH&oW+vUg$;H+z5QLHonXwG?pATFeD}9BpOk+**)Qqxv+jb0oCPgh5=%Z#(g~E zJq5?IS#7=U%69-&xm&F&4Qlm%`!_F8*1%S_6e)1oeP39iTKMA&`F?pM8L0qo>NS;6 zOY+5a!$>><16OEUJ`8NK(Dq~7T_+f(DRGf3LqDI(Wn9iuPIz3RD$kB=Q7|w$u?FlW zslO8JVq3w{kg>CQ>t3@6SWASz&lxtvyTx}1Tyz!TZX_mifrn;m>Ge0vw$tJLM0DM@LkuOfJJl^C z3S9S~B1wd?K?h2>jIT7(gOgqo0gE**b>rR8a9HH(Cn=B^W}V+SYvP(tQaw$b28Te} z4l9#w?<#T0QIQ(#C8uC3?Qv8$mi=tuudED47p4=$VVZE;XAfKYd}m)AoJ=45QG$TF zQM#dU5^qzU@{lT+RQJ1!90cpIGC4Q{cz_9 z@uABR0GT5NIwo5?*>{hzk~w*lTLu~;b9P?SfOWL&o>gmh=O>wDsyNpTmz+N~GZ2Qe z{k}dzwl@IY%v|O-o5>GyevXbZ(P?`Ox5&*O_2=!dYJ&B<^fmW=zn?9sD$ezjuMsH(nFiNmD<6bqB9zvCQCWYH2bmRK7WhcyBD|eTmFX8ZuF9` zyV$VuYf<432LGezEW?^?8!-GTN;gtcj+8Fxl7Xavba%IO^QhE-0R!ojav0}L*iwRS&UFQN4&Y-G%ZP9^dHu^!}z zFwnVXAdZ*ETuQMNNF=$_orDvtm*~!OwPx6-F`whwif~kOfUz&9LYk4sNp`vdM6Ig4 zr5sqG{vnA)lyl^ zGA|f2Ijbml19=ba@nIS!+Xc+6@KNzfLn|?uU-(Vz31-%VH|9EL95ck}sJ+(Woe@ml zQ;qVVFNiiS>{ug0o>Jj%=5o}Q(l^4MxYG;+LEMv#nPl{(9f={`cNvY^rQ2Am%91pK zn&oPJy>u?pGIFguhf_r2z9p2mg7Zwjk(nwn65%P|oA>xrsV8S)-+|)s{Ab?zF1n7+ z>nog6gD|<;d1|8x2|q4xOJjG5{yX1@5Owc2jGZq6Pg$9hHzxk)9w8 zQlgCwU;E(vk#FM-I}l1Tb|-NAiJUzPpErz9ZcIgusP#K;=fQf^JI~%e?}p!b1)8Cyd{JaNh>jTZp$4;M(2(2LDXoYWc_;sd?b7f<@qe$0UC+Dp zYORbOL)s?X@^r#y2RC-Oa~30o6hL<@{`^h1CYGM(974sm?(GCe2To#15N=Ib{v{r; z{}vi8U-pj{7|@o7PitjB0it1LQe!3JdFkbf*@xyKrbkL8EzZ(bOxEM1-*!fNVu8^| z_QF9bQ%q}GIIhCA)dvZ8O_(Dq%!wK6VJ_e(bfj`^&n)GSwB{m+wT{v310R^Am)#cY z9$Zfh)$$YQ1f}Ve+r=!94qGt@1Ij$q;I=b&X6WatpL@3m@ z*mWYY)E;hB+y%*qdDfcy>zxDtOD#h;N1lDU=q)qsFG9nu18uBZ#|q9mJ(Ww&I@fX- z-bB)zL)iZrf^xJJ&vicZ)Qf^DlGV{?XW-LD%rwpyHTX8eb@-8K$9LAZ>w_%`0#&Kk zEcUxP=sa}Qm?UNmh6-X%SFu>R*_(xL4!e?ZdzRT4eS9r$*|xZV^8Pu4^zU0=(Fv5g=7=LX`k=?cnKNUY z6+012)qbDi*QH~+)i^0eV(?SM{hgO$5zf7r@3@h!W$B}u$~-zK5)ec|CT+CY_%BmXMA5~E9Phl|23&!&pBamoIavLbhxQ$qh88bnx20(Y98AJ zL(x;*XQ<>U4FAe-z(}~Z<_@VQyKT8log|K+Nblt zo6Fh`Sw>?yOB_!9%JPS$#0M{nj9OnSF*DF*h33?Eb8$O+`2Fd`JgukctnaQ_cQq_# zFvC&ESBKj~|MUj|XXz7f&Wq|1XYB(?(U61;b zR={8|5sR6C71crFkvhw+#--3tQTj4{WPrT>{9Nn(b~NkJlSfQ)oEL!8Ju}9+ zD!@tjE()o9C|Zbg@;$gb z&Xa{5UUq$N;av0+1!m7`@O42gO-}gF)FVGkiRGMm$&RVceO`D6UVMjC*nmQbpdmM{ zV0-2>3DuCY`AVok@w_rt)#jpmRwY9n-8IT|wci$nwzAVJ$`{OZK zlfXN{StM3W;bn{Qbe3cnmdI&g7@7DK^)&%e?@MI8eEf(GsQdQTC?LM;k4FGP_IHX6 zI8=@%(J7FE^$Q=W}VB4)wk0JXNm6#(SP%*?fflsNxB+Y4EwG&Z>&AXQ4r{PQaLr)wB(+4y}3hnC{Kh zKjmCNv+Q<>>AhKcq&K*5DbmsPmRuHB#v>#zBI0<_TKH5b!MNim@7;dXWcl5^$1esN zkWUbZR>(l({Pe{*6>@H>jv!wkXhx4+`i>X%H#`yyxTmZrzlrNX$FJc~bqkp!-JZeD zQNmZn3`ml>@YjoU!aa;ABmZ(vT86yN7n1{|z09vfP(wQKEEAnC8kZ69TN* z$phxNwUrU;y$dwFUG9Vum96ID>vq2?eo*5)&tuZe?Mek=*tg&kOhyr0R5T05JkL*T zwZo0(t>HwKZj?xtgEN+hN{^?b>;jr!=r<~V^T=b9JePxeg4d6v4W?0Bf_#R93$~G= zF%*xFnF2TN@||lnTD~Bm!_aWxC%F*E6`tmcbfRA{T9AmUoC{&6Hi7adnay z^XLz%sW@joj(9ZRWZwxIEDMpC+qUBcg- z0S_GPSmq5d;-6yy<>$7ZL4_O#lhFH6j#rqKhpqj3re5|y;4_ZW$6tTre+4*d>2g|b z9ONUvO)~P{+7S5v$h)U(;t8q$MFkR~*tZ!rV7Dw(ar!-Bq>SN{6XKKenZ7cg zJEJ9*t}V4TvmI9!Lf-P48Kem`34Tz{No}cLBA~sLVs-E&745AXPf{kyyn8z9=|9;0 zPI{n}>KZHLBqaL;*;8)tr7X-^?~gbI{9-TPZ9_@MekNB1N9)!L-2<}PP?)Dr&0__( zQ~@zk4AD}xk!I>KZotfNz?!UB$LPBT=3%WEnIuxlB#5JtxrTEzd@pt@)fUORquT$w z6H!np>Sqv=G2VU?4I4~1I|BI8VjsD;{TfsGk8g;oeNHiyE58pqW!|-;hD-!Oy?rMI z^`G5Dd!rGG1oo1ffLUJTCC9nlv58VSznPt&t@F|v(GLFU80Q0dRgeexyg#Eu7zE;& z3_(;ys8afbhLD` zoP5hTkuBtIWv&lA)qrgK}6dlCP!RU!SKk zI(K#_D2nGsJkKL2s?b>tk%VC)x2aivq zac;DLDuzJ7<@dN<3pR2hy!}F_>j*Jl=k%zB20rq{6e4Rn?g<~ItCwMP>_rWrZKAaV zVF>i^!m{5qkBoBCu2TYd;ZHpg%cO(;RxD(%z5vgqlM%8A0JpDF4-!fxuI!i)im zuSh0j=!!+FRbAFFKTwb9Mj@L&C3^Vly22ohqUuZ$mu#DU>a5b&Uu2;9Eoy8Ep$y3s z&}f=tOq)k+VPJ2sZrc4fuIhf&oFJ|t#?mq4^#yLoPEO}?74nHi<}crTt*I-1U)DjQ z*dhET1Ll2e(RWGaZ%w7kz0@L`B&OAlb(>uID9eS}sC?S~6LK-wQ0pPnocvQ~+uvd{ z#6qjZc)K?GsiRSd<7v<_iWqO281L=|aOQOp4O~1l_eA$$E|0E)xpKd4HF`>RIYfkg z^5r>8RwX)99V%ZsajAl^C6$AvNrI2Fw$H^+4x1@3hffzksGU@t^b!-Xr|o)Ir^|Lr zI&@W=6`zY=60H4mfDF2eXAVhxYSWP0T6!+oKc+>dqF)@-$a36$7BRSeeIw=yaJ}8r zKGYfSkxLYX#uWa{wDq$|=K8Cp4#@vlAM+9GRPr-qVUq`j8s`c**mniq!031i>1S4hP=-SREh~*8OZX#I!_f zb_PCWOTM;UZ{nw-8SHROGd*FhU}z&)j&9H*6PF|{YS)Q^HQx?i8Ze#xSybFyidftZ za~eCE)7Gh}lf@*SYzh9L#WhRU`}cTG@q{a>ss)$E&$!lp-n_>xMpw9OWTr4zw!xY$ z$pd&%c+H#0<@=94 zs!ewKhiUvF!v5YHZFW0y)y|r9WBb@vwHh^+gYjDtkYUY?$Wbv$(Toq7*;q_#(IO?# z21|>{9ajxKEQu0EEu6h^aUGP#3@_HE%cI=rmo?JrjR4D{YJH=jS!%bIjBhrpRe^ZY zihqwu{cGp6xV>tM7lt2z?qzm)L(wWw5!CBkJv>;oqmqCYOg(d2_!U7J?aiW+Ep}NG zNhBN%kP&Ksq})zN#6}mGbnYRi)u(chqh;n`z@P`l303kSg&p#P)MBVM0*%=&Zem2s zhexc|Ks)l3L~s**OA&@Zv|8SM8OonZ9nq??sDMgL2wXoi^QE=ury^HysOudhO(|x6bqc1gQr>Q?EbV1L4Lz0lz<4W zxFI$WbI(wa?RufkWYsu4_6XHIaC2I>*^lMyM1Dt{(ol%pnXW8U=jo&WU*4yG`)(y< zKH1s-uE#<$xkVWBJ`ZZ4S9D%?44j^|2GS|lzHxqQ-8if-WRd~H&*J$8!SlA-mt0RAvK6;!OJBb7v@1t*|N-AH1&IOa;AEiuU5PZuh18n1(%j@R1w%y{>C+I0mAhb526lBx-4Ef}~xA`i1wNwm<6 z(!xCoLv*_5%Irm~0+DfrDIR>am#(ezuR+TVhT;r8M9!o@=ff4%s+8(lv>JaUT*O?L zETh@gg>hMecN^j|vv0^vCPiRYds+w=eEpo+VAlJV9OPr_z6lIFiVn4Jp6CY234Gpb z7Jq)};4i|eZB9`s8at9jpxs*#hNMI1dX>by{oV9)`te;{_dA!Eqn}u=&rJbKBBcu$ zG`B*aoLrltjb^ttC=B5XzOFVC6g#E1Utk4Brj#P>eT^(b=KMUAD+0HDuRrjH>9d_v z`IpEb313uxnSidB#~0o2k}7OH?JDJl<>Uled(o`dU>zOXRYW^>80_CIzh9rWu>{B{PLjo}8TLGfebvHy;-#P(@&0#J$`7^Nk1)^N4_$O#{kcYTRBosSv8E$d+dZC(%oB zd%T*Dl&d$F_F5cMzM@$#zPGfFX#O7a101&?3ECrm4>*Sf_d+}4;#iFor)|0s1OY=# zM_F9Gw0IxR7T@Q6fc})2XE;x(B0_PEuofssYI5*rNgpyfMu>$D_A_Oy^EM+=vZvvs zk6eVhW#^ZRs9T=KZKeV5ggtbsPW)SW?dIE5&)SQRDQdnhY(^#0{xNMat^GRONx@ov z)Cv>4=^;~DT18Xyb`8bP`X`Bq*B{}xu=5!G=}p}#^SnKjDB$VTQs|eqeCaP_8h=-k z3gDz%|I{Ak`g5Pyisn}83@@)ZQ;;)FsN3FLNIoPY#v(7OStRBqdA;Ozg-^iRvq9)%gHv(lDP1|JKjR4ETWR(J zbvpLT5?1EGZG*OaToZhwDB?thB#2$TJK!Ggp&?hY5J7f%9jSW1Fv~AOgQz?Jy-(%8 zKbKCur9Z&ZhJDk_8+yZ7AkdvuW{BPAEg0S>Ir%i@1ehL`gnqd#40^HSUFSjS!uSog zPCBa@YLOnQW_XbuN6b#&({WE^-jRY68vnP5h@n@$6=Rnl7waSPc_064-;LzY<@8sb1IReVfKTSbX9i&gRt z`{270TEVrMWoCowr}$4{WNr8ZV_h|+^2ZVTT7KBgZXS(_mDBiFl5Yt)K2c*=xQsz4 zWB3e1by#twOJ7k2>i;Z$htwFyR~Yq#7KA&m6IE-KTgSjsur?jhbpa0;JEL@S>RCPZ z824YM(W}o93$rUj>2kRgYPgeH=!MLuIEgrsyj|E8(OnP(BNL5<2i-)cJ=b6vl*t0R zf|$3HOOKagwb<)(xt3q(~7v{8fSUhutNy^8Cv&S@CnQ{{CSTqx##tgH-(tV-xklruE z;-JXh@E4EO6*_tBY#1KXr-%Ub*iVuHfew_FSnnTO9HJzVN<+yWszno!3v5JR?mEd8 z2M2@5qa_C5rCu7Z$GV~~5*KoOx%fKEEPX<$9rgzC%t#NHZZ<+d!W;cs-le!IhopZC z;QBL?>l=ZB8T-`kw^vm&`4>rbaW2ScQFRw@A#_4&GA9uRHYr zb?JVwza!~bYg&A%vd%;Qy82&n%0hm>FldppAE`|8Xu9!Aq-oBn^mDPYyIj;Akdk(g zLrOKWF4ahyVVi_)Ds@Z5(1QVXMyn2h^Y{s)&I8+?vJ`!VQ2;j!<&gUoiiSu_F$Ncfai#(Mry`E2J2_ zwq6P*=9uKn67B|vY(T1z+m5hUGmzo46i&SrcJYg8*TIwvrFPYgqu1?y(UZP#W&!mxl>+k#aZ5TKZT zf~=j-?U9alRneylTy@b_s0{Ml%{5R>OU=ACd*899@;rhTb_tz9q( zSvYDi;q7{VERTpFVxPpP_ZmH>rB|vxk%CkT_qn2j{8W%Xd#VRa)*|s!ONSj~${%N0 zw$e3+gMhCn$crP|dlbbch>U3xh&&cX{1Rj~tZ~ks*AskI z07_tS#9_v2DFNxiokd%0`;hXwBK3gSa4h*~XDuGDE);t6td+NwtQaj5Alb}+@ADv5 zE9`wa9XnDO4VE!RloY8NW3J_dhd~Eo8?mIm`Cubqw0em|nh|R_Buou(iiQ2&ht%ak z6uw~3!VF-bFdlW;& z<-`mABZGM-RoqZ~7k=PEo1Ad;n5H<2aWfwMlp6jTJ@&n9B(>;DA9bDhK2r(Q z#u_oYuMiFHxg6!gGH@s5(S>Z!*~jPv#5HLj4s5Xp_IV#1bLIgJYmJf!Y6 z(te?|P%2ot5K)C~i>T_jBFwLMYSQbFp!3PG<~SJE+y=`u?#N=^OCm$RmI3eZE0z`@ zlx~hI$WndgntIle5DVe(*T^#>`+6g+u+%G=Tp>t&`6vyc5uv7P=)&IHf&KYzvJ=XM zSUO)D%XgZ^#z28^R=yf>3v7qVcQ5z0byP3LX(O^yLC%s@xbaXpbLqVr@Wou3i4|v^ z3@mMBvwgweD)c~=Mb~>#>}A?1D09K&a3LcdI~VTrp4(gXo$x95D}Z@>;*X8pr#77r z)~pJOpjh)s{*Tz!h7$!rUv2}c7hhk3tC7}HP2b6oIzUtw2o#;Z);DJD>rMa7PWui&o1g2t2A?Fu%aTr*B8kg#L1;@!X0HS$9NA@Nfl4Izs6 zxGB?YkFxS1fOU0qtg4Le7~aMu+0-&wEcsVqz&J`hhg1y9<*s}Th0MA?>awGnq~N{b z?_!TXtOYK+7zLHXj7=LoDlIu!Z0y?cLJ`MRc5}&xWZFM}ch(pU2u%%xXO0Q$sBzAN zgEtiUUV<%VKm!D3HIk5I&z{LyA>zS<@TQrE_tIM88Wrm6E>G=+!VFX!-%jP4zg$Lv zfJ>9#S%T&V2yXfwMcFBmkqPUsB_p4s?%ry-xuZgPG5RuDDD0Yt|8SC5ymXSb_+;Lt z4jF!5x|5KA%r!b%=D@l8&$f1eX8vBN1fAp63h$KuE3ef|HLeJ%<4x*ATDRzXI*i!Y z;a28M2?tMHyo2!7!BNCIPs+vR>USHR~Am< z+4j-2KXlKiMq?IIepg_YXFsW{Asl5*_~(wo=NmVBIEvi4Y$7F?Fu_qq(l{|-(i$V& z$6SM67cLgB$;>%Ie%B}LK#Qk*tD{5&>W+|b@hB!FZz(AZ9UW7I{b5L2BQHTt1%4qK z!>)>R`=No)^h*C?ck>w=zcq8$Y~fB}=T8N!H;u|*l94MoMt)nZ;;ZU&@o+Do^UZho z#jgC9D97!uYj2|>ZJNSjW5OyRw8QH=aVhUvCY81T`%zIiQR2)ZF<(Po5R9#jWz~?s z%8=|*64sz_(uF62QIA?##%q-dH{G>zF6ZZ-uMcHCWB6Y)x9rC zXWCN!J()N1)nm$?h*#s^+$2asNyKLH#LE+R=Jjs=sar?mOQ~d4#sSndc;ajZH;QNm z8H&CN)9)ak_q%!J+c2~efqSg+;y5akjLNI1&fw+b6jOSfmpvn~xLQTsX- z0%JA}+5T8po$WLzBzlsMbkuLA;XLjoH^ng&Fjl5JP*sgF#VfkUPB%)A)ar0F#@JwN zs<9{smL?9X-cr6vLqf!~JP!(9xJ^#VunOD1MzxBwl^XA{aVTYS9{h~BOoUR3)g6`x zg@Z)Z4H1{bk07AI&rn?yBqlh#Oc00V!6DGqtP9!rtLxN|)O>?_&3wP|LXBjrcFQ!k zogBj@8;vW5l1@uydyHqge4Be=m59fIg`yORa4B!57BDcfr?3wpuuYL%IxS2cGu;NH zT+M~-a(i?Ih6#zZzHA89FE_$|iduc%-u#C&D8TxK<^i8guGl1tS?A0<0xTg=IjR8@ z$6z%+Efx&{#h>rw#Xdy~q#hQT@8mMC!c6lP78C=Ux{N5UkPNC7f62 zkcwZ934L@{6&DeQ$yF}bH*yUq6#4r)M;tgNu$+b;?@nfzm-tw>8|8o#Yopr^lh^TK zYiw0NJ%Q4hV1w%A;E70=6k3J(Q7kRmr}VJksm)hu)~SWeY^j4*t{%)WiV<{+5nFJYc+|jI*7jt*tTY+eguy0JKL-k zJ)uu9LRrD&L!CSxqB}oD(He99FX7rO=qfoi+A?iA@udtI+7U zL9}pdrjiYxPve!vw$jleh%w8K#2|uQTD`wM;Lvif{EY)KUxf&HBWOngn~WkAa|pHo z(xy0oU2oHb@cmiSzHBA-&*c+?H0@q-&VGHYCRSf$mL8?M362wU+}9l>%Z|zFGnV|! ztr}tl1L>eGW-BWh!EIA1A=t*p+`avdU0AhZL8Q*z@Na>8ptJ9VdDy4k|3q4`Y%xR} z-kQ1D;_v30dOF&YICo|2bGz_+Rbc8|umVX=IOtzOFGxtNRuWq6KOGr_=1ExX#%B}# z$fS`X(X$1wNdx}mMO;bD>r+Gf`~jOsMwWVa8Y$ZLQC@x_gFZq0 z!U1O`N#8X_8*AD(anBPR9R#!ExyLXl$8+s`@bThAb( z3YXXG)Bd4G7=lWgP6LsJ!2&Y9e}CmK7-|3 zy5&2T-UN@t=+yGcb~SpmE2{z*c(4yHA2m18g5yA&jI*>-&T;bi`&(*M&yphMsi=lm ziBoD7oCPo*u81!sQWHVe`r^4(mG5Rc77|j8?QrqOMt+5;xw&Cf?f%(xI1e8YFqq(x z(dF<=im#xgv%}^MNAblOf&e%>Sg_l864yCW&B&=x=85pDv~b{PAJlGsXit9n+A+y) zvl0)?PpdIC69!5SZ2{OM{0dbooA}^kee&~7meqBl(RNY9e$xr5H zQJX)*xwJiOcf3HL6X11=@x8L22!0d8_oYU*=?2ma!w^7VQ>h#cub z)Z2{YarOIJlOro^CQ@pWKti+!dGU+w=xDz2g?u`{vU8mx8L(FQ8LkYWw$tRL8sm`M z@J!4MtjEibk36p)5MJYXVSdbSa$z=H7-bA8&ZsLR;?>!&Z5ViPmoN zma3$-PYmDpRI_ssN{>&mo=hUis-NnOCT<;>P0RyBW_mZ>z8bD5MET|_(=M@5Lu!1!8!b8DAT6Vbn2dF(i z@X$SP^LZ<0$*EoF>i7e9ID&~DT7Ij99ATz^6tjcA(1B1%X9-|O9 zmyS55*jmE(owww3Ck-LvBwxoACM#U4FVaV8C!z{nii=*}Q#ZZO-(&Nmsf2puz?t1Z2g(BDcuCm-# z>Ge?h*oP)5M0Ftv#G$PMvq&?`WeozeDQUzX^N|JS)gUSe75$-1#|Wzmw81jFx%$W} z$?+QNt0g@bq1C+)ifG;Ezh!3%Y(sXW>T?$7{MGB`LN_dGe!cdlHg>mi+^ zt}$Ww28z7AGBP#4)SI7X7#t;yxEE0EMU=U$$06(f@FPO$+Qhhu``iZu#F(F0A1xKF|LPO7oCiYyEXdz3aQ6 z9-#Rbsv`fVJl5J{8zt+{QKP}Uh19AsT?etTv<8y_#8bM`Nxm=wWa~onuWMldhKQ~{m9OreM<2wHM?0|UT7st2?A6{g?D8V|V;uZGg zj~YhAanwgI1!k>33Jjk7RV6B>Jz%IjpP+eC{#V!cl_QdHIw^7aO`E*W(I$<)`s5g*#~Kgs0Ju%AdZ>~A$;v^tirdm%Ay{09giuyx9= zS-LMkJCDr+s(P2m!7RhKB((DF*YM>9ZhE=S262R`c{iJXShMd2V03 zow7at2HQ1qDqcZo__Yf=T_QdPX{j4AA=;c~4p{{<$J5FPu5y)b4t(z5hHcD=oNx~y7`h5S7dBwUa0tLxCw)4sDEiU^h zwq;)!9hO|XI9f!bZ>D#_p zU1@sF%u4~bdz=7VI!7;R|M_8Z`&XeG^-}jr3oSenR9d3nDgZoCC6P;sTR%A;@+@0I z5Y315$`~)rEx3Uo_r^C?sqanXf>0kUYE~(?dV$-HuOC5BQf=xctsAY?ho7F$Wp{$& zm}gq^VTM^vPb2{?OP60EH5TpCs?XAmr-gBUL!<(GQdQe z*9AjmWL@6z$|#ZOU5UZ@yR%i&;REVM#)YUrIl~j_WNxihh5wM`&FdO&Pk)oJ&sfvz zPLTm0ijB!wMR#N*w0vRfqFT|feWqYk#=0XVnWA@iKu?@_Crd+x)goT2qfe$LzbBW# z3p!J?BzusR_Pe;X&BxhCNu}wT92tkO^s{%$BEt{@-rv2mBok5_jl(T^tB{WfyLSkuFZSp9B{3K@q|8Ph!O)Uc(4#_OM+mi?&U7E4tX=3?r3qwULOX zHg7^W;5?g6>h62UuRsb~&B3$uoekQ-AW=iwG?SSZW7^cCKO*AWY~?+)=rjHSpr{; zmIvcep@hsYOBa=Yj|L=^Q%tC)6N+9){wMOt*z>IVN_1G&G^Gpf>OqV2#*X@xeg`s<#&MQ6Z7cS7EQtHlH*e2U21dceH|9-| z1kpk*PAGI+zyDry*J0gx1;_sgtYCist3-6$^#w2KcC+|tL#BWi^lop!Y`dlwmsbZz zJgL}YFgPXW2xEPCoH3WyD^p7`g8DbmU}Z3Kt{{hrFbL7%beB#vS6N@^V)<%B_)a>g5aC#gtEljM zWYO;~$iQl8s{tsp@Mw9MsMMfyo%7M&419mSR1Ry|{|krK=EDZ5?2-mzi-7XPeFhMo zk<+W;MN-0s^Jmv4+`%z}CCkj4CW}QLV;yn*PDvTHZv8fY`PaE$Lp6#AbQ2Y$D!pEB zvsyW0%2OPwN_q2)Vrnf6Ws&oL3yT#Ub=8s7z}wd`3Z*f5W7|m1`)Mf&gm^t(q~OvrL;Q1#4&vOGwsWbIQ1`GFt&fdiRW?&R|?C ze~uyy$>qa8bT|h??B<=PY>r6Z7n*s^m)Q-C+F z$TpisOKx-UVA4&&rm1-8T;_OVFW2aTjZN|U$9c<3Vjkbw;Ef+XDn-ln+*^%3h{$XA z&)XH_qi>mBrT=d1QNjYm-0T}aa3}dHJ%1CyDqJ}Is>Ck2F_Jgf;1EiGv5LhFFwu$` zUL3Qfyui{;gLR0}Cq;FD)12iWcyr&J?Vu{Mky;2Tn#J5BiY=*FPKGgrld0;bYF$sz z&}E53q{H}bLp7=)AsgWYo*=$#F*`)h9&W~Ns*Z@W_DjW0$JfC3dMuH!s0u?+U&Yh{ zA5C$Zo?mc;(GLc&2uukhRs(qxli00*I9(&V5l_SjI`~R8TRSu0OXUVxxu+ax>){hD zVH@-`uom61sg6GQ?*LpeToQZeP2f^mp_@25thHUUDp#LVr!&AL?TOgodlSdxOE=Ym z=WFe-ZPtaUBLd`zk~%eI&d@FmZ!yMxuHGFMmGWs?f4Q~jbi5#0YHK-(ve|wm?m`_w z4>=}7TM$nQH8iMQc9OWMrK3n3ZpY=%z{_H<@4sXa+0o>Fb_~-Yl<<&i{q+XfuC3g2 z78dFsO@swSlku*F8OK~GLKmY5rOt=oa-npKc!lp^MrJQ8Q~Qren& ztl78z+yHIVcm9x#qRVqh3!y9JqqUBpCc`)gNua*`R-F_wI=ftbp@F(|$8-KMYm}qt zt8NPNCx;5z} z=4LqWG_GgCMaX)kzNp0*c3ZAre3;Yg;H3)|1je=GsQtmdm94dq`2I=Nrg@09OzL^^ zeT^)p0TPat+DmITjPsDOYtVDcqd#c#Zka^Q8}&AG$ZN2AhNSeP%6cXb2RGb8h4rYD zIHS&J3X4@}ICaKM2!rm6A0LH!2;@`m*TpW^di`^wUYDb%rAjHTh;mB}`YBzJt^T4a zKyk}qqNJ!hFrp<)g3a$5C*8IphL5_l>1$5R4p#Y=0Lr;tEyubE*5*#YsA5YDPMno= zI)$@<}Z-Tqm57p2J9$8zVER%9#xpE`wfivf=M%7ciD zRGE<883xCH^Vh$gMxEg`vkybaqu39`)Ubmib?R8ynf8UQ@6PX?KV>Z3q6nvZq!hcr&9s`iD58w3!m^uG_klOx`*l17zQ)I*bF!8 zwWYNe$xaGNsbmuFt&&iII=$l+9{ZgVcOg;H-yHZYJ}7Z9T3-s)O!{;#a`fUIta|h( zxXPsH1f1*yiSh-9YX0u+i8j@En(2CZQAll0NTwugmKJMSMsF)ilLRC4UM$9-ULM~n z(dVYk7CeZg%j0NuD|s_~E{6EmFXRoIn<)}@rph(SU zEAL_ub^yq1?Z#p4GXdSQHNW<3X(}C`1tIusYhKW~l}hDUAW>o(UW>n4KzlMdR2{4A*A2-8jsn+OFM0UTGCS3(0(*@thU#NpuE# z-wnswPTy!yIk&Wu_r0Lw-zM|vo%dGW+Yu8NK!DqcX!j_&tlUfa@_HXDl3(fV&#BUs zGi`bB$;5L^AO0KDYg3*g%dBIKAmHZrXt~ti9;p`P>OHvI6#{y1@6P{{G)cNs&@l_d z?^T3FBv&mZTwR1RTXwXW<7n=)&ugtY{jJkC2K4=F#qG^^{~4JTY$)dTKc7a@2~S z#pSk9YPJ7OE-4{YrNwFmvb}gTwzxFKoGiiX-@UWB2$zA9FR}}T`rlJ;^LmEi)0(3! zUj)040wO++cC0#us8dv0O1X)TaR!j$n(N6Lm$DZ1~fXj@aGHQjK@lF0IoB zWZt+(*wk(c!O|nw8j8Z)tpS%(Z|TX5jrH>B@N% zyw3R;4yS2*$*Fj9)THe!4lI;CUz{yFKi^#J!k1gGr-4(9=exbjtf%|s*8Se;<1Ay~ zt}ILZ;p`OaE=fg&=K17la(~&V1J0>LupDr`^?2Ua>~np2oCVyj-j`+hK40vX0cgPE z+0*3nO`Qto{>A*+!)DWVmT{Y(73el2jf~em*}6I4?h3oLAMwi0wbO_}*P?Cq18cs;p|W zHJhwrA%)|afz?TqZBLgHf%p11cR*d+z1Qjf@|rWCX#AXreDCS5?qPnpf4YtMetraa zytx~n7dU=?xOaZK%DTFi2=KnY>NGZLyK`<^eO}ILW$P%yd%jz}#XAQ4?+1#eh2NEl zKinW+wgG{@PY*AFr`z3i`;1_?@sjxd$H1GjFzvRdRj=o(#eD#n5(e6Q&mV@4#R32G zy~Xi1d+kM1x`%1t@oKk>CeY_846sjrT#tFa!70nktog)gwwd*`+R2%;_UuC-_cU<#X2N6l05cne+MCs{U!iqhryge-k~qRo&lo&gbjtqWjy+^D5l&ZQu#- z=a(4by7IXfe_jEr{mZIvU;X!A7wLch5&rkze_qb67MxBVj%JpwwocanE95GG>RPre zT-;qQuEE`d1_nYGpTq6v;yeMJblnlg|8$CSiyMVN%V^Sc&s@_S)8&z3qfPvZG@y)m z?AZWbXvxv{rJH5nLykX67WV{7OtdCFc+-U|xA52Xa%-YY4)#YYU~O!j=F^^+VT z%(H3B3)nmXpoY5jEF^YpOgV*}HxVT_u-ipTsbd?1uOx&k4dr09kuHh%0hFcZR%=BL3*D{5Etmhm!Q-uQ@Y7K8^Ci<7G=^XH=8D=bmLUCuC zF^FXR<398H5KG-)_HU0pRb7h|ekuio=4fJC0&0TSunQDlmU#9SsUNG{@JO(++Kd_C zpv!v$)M9CrIg8~;$11ZTBDhUKl?bLPIqTebCa$gd)Gn4P;OmdV-wao@brc@?`N&(e zX6c-gh3ToJXJ5)*VQxrw=t6MgC|e`~spy;$MlhUUP3{ShVT93OUrJop&RVK+yyMu|-k%&`8?=7I!=s0_Sv_uiG|T@k z7<;;qfYrh9oS`Qu;4Rv94d{mMPgz&bf#WNhy9Whs5=SaGb$p@swSFsn6mGx9!{^aC)RIo z@@cQ`X=AgQS*s^5`Xa!*0wL$Fpj?>UXPv&Yw6dKU%}^UPZaa-6r8T8@zdKcJC8WUY8M&-vR0ix ztS~uRIGbuLn8oIL#@R^jNGf__3`)Dl;+P;Z90*5y@ z)dviOG`+w;u=IzTobkoMfW4y|MQHlTfAedoh+8hCltQI)TBz)EiknqHDOJ@@R205t*t_j=AYURy z5mjXw`@%8l9J%~r$4Sb3yQP6vQn+m`Tw4&=p}9aJGigY4jLb*_$!Yg3E>0LL#mi~C zOVAuwweHt%kc!8(?1?aA*tsZ%&sF1BPfqBUE4XBqOoBl!^&1@tK6SK1HFpuofl0Tu z5t2T#{U@3IIT|X6bW3UiYbf(-x;8MJnr6$ZI^_iM5;IMGZQM(OZc-w~Booqx?)fT? zA-YIiLyYvn(WdK>=egw?^OfkD|6oe)vzH-8?L2{htBuWy3ZM5rp_9=kGm1}0y@8vmk)j<*d>!)aL{K@Dib8{FmX?b+?kpd z9uhH7#IC=VA6o{ z_Ru3_4O@?R3bSo&=T38_YOQs*Ge2c9bA-51$sLG;BDph$$28NG(>kR>kCJhP5P#D!a!FVisXGL_{W6XANlRN8!Le84;9nmxnHqje? zXF}E;mDyE~wcb{}_a##wzMAH-`mKl>EXMFZt(ze&CP~xQgtfNJ)B0XjGNN zc%nLB8h30JnH_Qvb)s#ItzZ(p8i6PZmNh%AH?}JxW@;CJr}b_*D1;(xew+QcBWmk7 zp-ZZ^>s#HIZP$*&v^L6el0YOJ^S%IvmIY*^CB=zk-P!DW`4MLYOUTbLdSWgR{AN3MWt3EeE6*MN+ zrB7Xw)B2*6*?6LEJ7gN%LA|#g#4HmQ{xVfd)5!NBB6H}c1-4S=Noz>NfdwYKlP(h} zV97o6Do>(xT0%^%*#A5`PSY}Y_Ep|}*|Y>qz1ZglSXDM-;vY72q?WvkY#WS-oeY~g z7ERLB9biFNkEZPT2wr0Wi%otGq=?yFby#xB&#t#Uj7B2OMPrdt=wME*hxK4*%AJIZ zntS`Y;Y>p3GW_y}+J-Ke+L3T`NsX z4j6!d4zOdV2v~6NZOm4ejVH(cWeS>M?a@X9iGi=N`Gkr^b~(Ldi_Ve2t@5Dao>$fx z!?8!RwG~@kHmC9uIMK;{o!HV&4aG$NG-xzhKh1UGhi>YpCg-XbOUbvp)M)+G{HJS$ zcNnp{(k1I1{EQxizq8e-a@RVka~e7;L?dm1djy(>A}ay6%(r5{$Y{Tw@o@D!$7W3S zj8yMjuIX;p`uKBOq<+R4AKF=g z??t6K1gftA_jN}ip^g*ykv}e4C3(SseP^7y0LRzvz0U(O-$^82DO4*;Sgv!O1>Up= zBvzam4K$51ob7!i{F;rXD&xj&H)@mdLn?stOyZ5vHteMS)4LQunuRl_jru0hbA>46 zi{S|5iHWc|nMds$tkKJnB?MEdL;?q~G80wz|()$=V{C@G<=InwN%7kH#GOwk_ zvwrw3qV@I%C0wckujoA|n+mn2>!N&u3&$=Br^dnWN4WPirO@}a@!RkB_Ajj_LT z(N*VNR%qykb>3`yG}UeerraaKHSesC=gZu9SmjL0nWr1H|fe#xaNpJfou<8H^RdZcivZRJ$G%UZt05^fau*`B#x zsNJ2JjuYv-lZ1LbvQ@V_?cQ_dG^O3V*47fSNLAZzHd+2ULp&qnS5{X>#un-Jv_<@` zPpYIVc0_L5Z)1b-sI65t>V!!zCXAFlLI#UOwjdJI@s((yY(S1yVNbjk zeH^@EnpEKbgYq}#rzlJ$OB{ow2o9(JDjSLas&K5+7VUqb0e^-1n};19zq%D-{`OblQx1bML($`b!0 z7=96#)Rk-b!Nz0W!-#3;HRS$%h}mw_oqdYaT=-AT(P}NEfQlx42hKZM87kgu(O381 zc4QVP-_V_j#=bYJ6F&NC=&`#_QXx4P5PjUoR#oI1*q!i*7`5h(vL$D#+EB*tj0LM= zW<8H`NwH`}Zk__HO|NTB!mQnwGQ3N<$Ki`ocjScC8MWaKM_#w_1ro3Q?KUcNz6cuE z@d*;S{jIh@Grl!kdC%TgJuV03)8_{IAzw+8NFt~OWKE4^>&CpXBdt}l!2KDL0%2T< z4#>p$3gi-v4y=NM4y+_~?t z>|F(+Iuoa&74r%Qxcd&LQKdhxODPPwT6GeuGbZ$Qs!E?#dCcn&&7X`eCv3_8UuFPK zu8jSegw#X;e`2%NITbjPUl;Bpt71f7RsC;AsVRRO|BCXy^1mD5QT|5?C+=xWgQspu zj!YaN0IyP<0oFpd4p75*CtppavIt_{zqt~wf@TAbt-kgc&af%z;NbqCXL%njUR^94TB6NykxU_zD1U9SKzzkUr$yT=49)S3 zgAQOap9}Am$+=^6k@?R|iRyEZz(Ji5uEmEIG_wzH&!F9NneK$AT-ow-(|Qu#O~Fd? zLH-rxx!pR}u*4|=95cx>?_lVl6VF#MJl?G8lPXJ0uY^XwYYLJ+$FyVQzAVXFoy=ZFL@PRkb0Q&G(bxA2mretlFQ< z*bXS!rq8zIl`VA{y3axswh01Sa*IDC^?Y5R)rZXH6T1xNO{~6YNoN~YsQ<1VZ!rv2 z=vm-2d`l8-__p@b7&|wr>RH{kEVWl+d?+9HNB3u|Pr2~X zk$pU}DWJN~((_M=y5hzwU)w7T4y%M%bfA#)b`O5sn)5Vi%^4HETUr~Fonvs}inyp* zVboH$d)>Aulptc8!zz6t?OIBJCOYc|1nA==Xe&Xc6OB#DkLH(aI}$P z+0!HIrLB(Ur92ie7NtZKK1|f6T@8QHQ>eT-4e=Dpkxu{OcD!%zY$4j|i~h;^>aE-< z#5!aQt1$t;|CAnT$UQ<;`X<;8ur*ASDTND0&l+ZLuROSV>TpBklk=JT;@F=Q+~H;d7;&M&Iwc6YSMA@f=qMU1RpT{h~F6#3YH8~)Aaa` z;PtIR{v+?oP$C!6ltr{D-_N}et`iH9jO1Elwsx|$k#j6uSGa&Gf*vf$F*)uNPtr_p zXW-fr2k{W-dG0_U2c&oW92-U)_wy-Ikm=;7ul?y0)(rvV7BNznv3v$R*9S)1YGsYx z%fXzuWu3pY{w`NjZ(@fJyP2@e@amllh{TD~7%j#6E3if!av$e<5 zTUupim0bSg2s}}TT&8Bfa?`_{T+zwIlJjgm28Qh`Y0QcP&!C%qw}<)R7QgQEfw!>a zdee2?$v*%SH-qfzdVj^)1(jtxm*ZAv!CwXgoBulr*MM;4EBHSo#QLuA(iG%k7VoTf6a3N{t!cX67LQ zF*~$zJS$X)Z(W*i;~>TspKDu2Mh;F_R`$BYoV@Bg#cstv8J?@^C%CL z;)4K^znfnxH{H3ywtgpFp6f7=kGn2?OI>w$#_}pR`tTkta44E^*!6|FUCZL)1m#(N z0$7z*Ih#lYW}ltSErfWp6}%D8^YnP{M%#0LZ^(g#8JcOubiYc;cn0KKIz^8h4C7PY#oS>hq&)3(x7Q9ooo^vz zXwi=Ggn26b?6viS-QXFy>{+hy!R7d$FI}t#<=ZYXly?H(hT^rF?ycVZs!N)=S-Qh9 zHFGfo9u9tk>r(y*Ubu^VN2O`t=ejshVt**N=4 z%F}9cTNV*5bh6e6NOtuM=|DT6*fo;=Zqo)QlYoC1#jh}etb-_O-b!AvG&Q{Gw$YzQ zi%#s!OotW{&eY z=QHbc-l=V2X{!ky*crhZ${rt6A|E3LWxGE#wa&dNH#F zAP!qAR1qzQWO0X4w@aZ}mqli+rM5D><&{-M6W1(<&pPH$)l z28ij|!TVnO7C2%Ejv)Bk_*YBp`%ksT-qj9Tj{yR+W!EgcBtt?#;D$%X{}=3y|39&> z?5*wWJ)w=5AQCDcNZvDbJOqTxXa5eD2!dK;f+Rrg3DA5@5DV3AKc_wYOGE^2Pt<=) z!Z;PWjR}$jRW3s5u|U#DT}z12=~YB9G!P5K4nkUiR>1Ef5<#u8K%~$|EO^v^7o7ep zPu(>r4>pL2>OVJ>%McLY>HW8oy9IT_28n|v&!IKgAQ=$D74+m!#$U4uBK#5I1NE0M4+P()_y7O^ diff --git a/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json b/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json index 85c43b21..388407c4 100644 --- a/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json +++ b/XEngine_Release/XEngine_Config/XEngine_VersionConfig.json @@ -1,5 +1,7 @@ { "XVer":[ + "3.24.0.1001 Build20260927", + "3.23.0.1001 Build20260807", "3.22.0.1001 Build20260427", "3.21.0.1001 Build20250923", "3.20.0.1001 Build20250620", From 205c656b84aa9038d79e6dfcfaef96772d0fcdd3 Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sun, 27 Sep 2026 16:12:00 +0800 Subject: [PATCH 32/33] update:depend library --- XEngine_Source/XEngine_DependLibrary | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XEngine_Source/XEngine_DependLibrary b/XEngine_Source/XEngine_DependLibrary index fbb0d6f4..d3965d2e 160000 --- a/XEngine_Source/XEngine_DependLibrary +++ b/XEngine_Source/XEngine_DependLibrary @@ -1 +1 @@ -Subproject commit fbb0d6f4a83d2983c053750a146d663dc1932bc5 +Subproject commit d3965d2e9584a0cea2af95ca07a6dbfb3758bc95 From 7faa48a1b3f6ef2c6b7f98d91ad95d0846480a4c Mon Sep 17 00:00:00 2001 From: qyt <486179@qq.com> Date: Sun, 27 Sep 2026 16:23:18 +0800 Subject: [PATCH 33/33] fixed:build warn --- XEngine_Source/StorageModule_APIHelp/pch.h | 4 ++-- XEngine_Source/StorageModule_BTorrent/pch.h | 4 ++-- XEngine_Source/StorageModule_Config/pch.h | 4 ++-- XEngine_Source/StorageModule_Database/pch.h | 2 +- XEngine_Source/StorageModule_Protocol/pch.h | 4 ++-- XEngine_Source/StorageModule_Session/pch.h | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/XEngine_Source/StorageModule_APIHelp/pch.h b/XEngine_Source/StorageModule_APIHelp/pch.h index 4da4d077..a47bed49 100644 --- a/XEngine_Source/StorageModule_APIHelp/pch.h +++ b/XEngine_Source/StorageModule_APIHelp/pch.h @@ -17,7 +17,6 @@ #include #include #endif -#endif //PCH_H #include #include #include @@ -81,4 +80,5 @@ extern XLONG APIHelp_dwErrorCode; #pragma comment(lib,"../Release/jsoncpp.lib") #endif #endif -#endif \ No newline at end of file +#endif +#endif //PCH_H \ No newline at end of file diff --git a/XEngine_Source/StorageModule_BTorrent/pch.h b/XEngine_Source/StorageModule_BTorrent/pch.h index e45d76b1..7fdb0e4c 100644 --- a/XEngine_Source/StorageModule_BTorrent/pch.h +++ b/XEngine_Source/StorageModule_BTorrent/pch.h @@ -12,7 +12,6 @@ #include "framework.h" #include #endif -#endif //PCH_H #include #include #include @@ -58,4 +57,5 @@ extern XLONG BTDload_dwErrorCode; #ifdef _MSC_BUILD #pragma comment(lib,"XEngine_BaseLib/XEngine_BaseLib.lib") -#endif \ No newline at end of file +#endif +#endif //PCH_H \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Config/pch.h b/XEngine_Source/StorageModule_Config/pch.h index d506fe0c..ffedd7a9 100644 --- a/XEngine_Source/StorageModule_Config/pch.h +++ b/XEngine_Source/StorageModule_Config/pch.h @@ -12,7 +12,6 @@ #include "framework.h" #include #endif -#endif //PCH_H #include #include #include @@ -58,4 +57,5 @@ extern XLONG Config_dwErrorCode; #pragma comment(lib,"../Release/jsoncpp.lib") #endif #endif -#endif \ No newline at end of file +#endif +#endif //PCH_H \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Database/pch.h b/XEngine_Source/StorageModule_Database/pch.h index 188a06c9..86611a97 100644 --- a/XEngine_Source/StorageModule_Database/pch.h +++ b/XEngine_Source/StorageModule_Database/pch.h @@ -21,7 +21,6 @@ #include #include #endif -#endif //PCH_H #include #include #include @@ -71,3 +70,4 @@ extern XLONG Database_dwErrorCode; #pragma comment(lib,"XEngine_HelpComponents/HelpComponents_DataBase.lib") #pragma comment(lib,"XEngine_SystemSdk/XEngine_SystemApi.lib") #endif +#endif //PCH_H \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Protocol/pch.h b/XEngine_Source/StorageModule_Protocol/pch.h index b0b23c1d..2445a372 100644 --- a/XEngine_Source/StorageModule_Protocol/pch.h +++ b/XEngine_Source/StorageModule_Protocol/pch.h @@ -23,7 +23,6 @@ #include #include #endif -#endif //PCH_H #include #include #include @@ -102,4 +101,5 @@ extern XLONG Protocol_dwErrorCode; #pragma comment(lib,"../Release/StorageModule_APIHelp.lib") #endif #endif -#endif \ No newline at end of file +#endif +#endif //PCH_H \ No newline at end of file diff --git a/XEngine_Source/StorageModule_Session/pch.h b/XEngine_Source/StorageModule_Session/pch.h index 5ac8b7c7..8c291a23 100644 --- a/XEngine_Source/StorageModule_Session/pch.h +++ b/XEngine_Source/StorageModule_Session/pch.h @@ -15,7 +15,6 @@ #else #include #endif -#endif //PCH_H #include #include #include @@ -58,4 +57,5 @@ extern XLONG Session_dwErrorCode; #ifdef _MSC_BUILD #pragma comment(lib,"XEngine_BaseLib/XEngine_BaseLib.lib") #pragma comment(lib,"XEngine_NetHelp/NetHelp_APIAddr.lib") -#endif \ No newline at end of file +#endif +#endif //PCH_H \ No newline at end of file