Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
serr | 86ff8bc787 |
|
@ -1,529 +0,0 @@
|
|||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <locale.h>
|
||||
#include <zip.h>
|
||||
|
||||
#define MMAX_PATH MAX_PATH
|
||||
// название сервиса
|
||||
#define serviceName _T("aaaa_ARCHIVER")
|
||||
// путь к конфигу
|
||||
#define configFile _T("C:/Users/user/Desktop/LIBZIP_ARCHIEVER_SERVICE/config.txt")
|
||||
// путь к файлу с логами
|
||||
#define logfileName _T("C:/Users/user/Desktop/LIBZIP_ARCHIEVER_SERVICE/log.txt")
|
||||
// путь к исполняемому файлу
|
||||
#define Path _T("C:/Users/user/Desktop/LIBZIP_ARCHIEVER_SERVICE/x64/Debug/test_libzip_library.exe")
|
||||
|
||||
// функции для архивирования
|
||||
int isMatch(wchar_t* str,
|
||||
wchar_t* pattern);
|
||||
|
||||
void consoleEncodingNormalize(void);
|
||||
|
||||
void zipDir(wchar_t* zipDirPath,
|
||||
wchar_t* zipDirArchive,
|
||||
wchar_t** masks);
|
||||
|
||||
void _zipDir(wchar_t* directory,
|
||||
zip_t* zipArchive,
|
||||
wchar_t* baseDirectory,
|
||||
wchar_t** masks);
|
||||
|
||||
int readConfig(const wchar_t* filename,
|
||||
wchar_t* zipDirPath,
|
||||
wchar_t* zipDirArchive,
|
||||
wchar_t** masks);
|
||||
|
||||
|
||||
// функции для работы сервиса
|
||||
void ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
void ControlHandler(DWORD request);
|
||||
int addLogMessage(wchar_t* str, int code);
|
||||
int installService();
|
||||
int removeService();
|
||||
int startService();
|
||||
int initService();
|
||||
|
||||
enum ERRORS {
|
||||
ALLOC_COLS_ERR = -20,
|
||||
ALLOC_ROWS_ERR,
|
||||
CONF_READ_ERR
|
||||
};
|
||||
|
||||
#define MMAX_ROWS 20 // максимальное кол-во масок
|
||||
#define COLS MMAX_PATH
|
||||
|
||||
SERVICE_STATUS serviceStatus;
|
||||
SERVICE_STATUS_HANDLE hStatus;
|
||||
LPTSTR servicePath = Path;
|
||||
HANDLE hServiceEvent;
|
||||
|
||||
int _tmain(int argc, _TCHAR* argv[]) {
|
||||
consoleEncodingNormalize();
|
||||
if (argc - 1 == 0) {
|
||||
SERVICE_TABLE_ENTRY ServiceTable[] =
|
||||
{
|
||||
{(LPWSTR)serviceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
|
||||
{NULL, NULL}
|
||||
};
|
||||
if (!StartServiceCtrlDispatcher(ServiceTable)) {
|
||||
addLogMessage(L"Error: StartServiceCtrlDispatcher", GetLastError());
|
||||
}
|
||||
}
|
||||
else if (wcscmp(argv[argc - 1], _T("stop")) == 0) { // осталось
|
||||
stopService();
|
||||
}
|
||||
else if (wcscmp(argv[argc - 1], _T("install")) == 0) {
|
||||
installService();
|
||||
}
|
||||
else if (wcscmp(argv[argc - 1], _T("remove")) == 0) {
|
||||
removeService();
|
||||
}
|
||||
else if (wcscmp(argv[argc - 1], _T("start")) == 0) {
|
||||
startService();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ServiceMain(DWORD argc, LPTSTR* argv) {
|
||||
addLogMessage(L"\nЯ в ServiceMain", 1);
|
||||
int error;
|
||||
int i = 0;
|
||||
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
serviceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
|
||||
serviceStatus.dwWin32ExitCode = 0;
|
||||
serviceStatus.dwServiceSpecificExitCode = 0;
|
||||
serviceStatus.dwCheckPoint = 0;
|
||||
serviceStatus.dwWaitHint = 0;
|
||||
hStatus = RegisterServiceCtrlHandler(serviceName, (LPHANDLER_FUNCTION)ControlHandler);
|
||||
if (hStatus == (SERVICE_STATUS_HANDLE)0) {
|
||||
return;
|
||||
}
|
||||
error = initService();
|
||||
if (error) {
|
||||
serviceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
serviceStatus.dwWin32ExitCode = -1;
|
||||
SetServiceStatus(hStatus, &serviceStatus);
|
||||
return;
|
||||
}
|
||||
serviceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
SetServiceStatus(hStatus, &serviceStatus);
|
||||
wchar_t zipDirPath[MMAX_PATH];
|
||||
wchar_t zipDirArchive[MMAX_PATH];
|
||||
wchar_t** masks;
|
||||
|
||||
// аллокация памяти под маски
|
||||
if ((masks = (wchar_t**)calloc(MMAX_ROWS, sizeof(wchar_t*))) != NULL) {
|
||||
for (int i = 0; i < MMAX_ROWS; i++) {
|
||||
if ((masks[i] = (wchar_t*)calloc(COLS, sizeof(wchar_t))) == NULL) {
|
||||
exit(ALLOC_COLS_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
exit(ALLOC_ROWS_ERR);
|
||||
}
|
||||
|
||||
if (readConfig(configFile, zipDirPath, zipDirArchive, masks) == 0) {
|
||||
exit(CONF_READ_ERR);
|
||||
}
|
||||
|
||||
addLogMessage(zipDirPath, 228);
|
||||
addLogMessage(zipDirArchive, 228);
|
||||
|
||||
while (serviceStatus.dwCurrentState == SERVICE_RUNNING)
|
||||
{
|
||||
addLogMessage(L"Работает", 0);
|
||||
zipDir(zipDirPath, zipDirArchive, masks); //
|
||||
addLogMessage(L"Vishel iz zipDir", 228);
|
||||
Sleep(5000);
|
||||
i++;
|
||||
}
|
||||
// mem clear
|
||||
for (int i = 0; i < MMAX_ROWS; i++) {
|
||||
free((void*)masks[i]);
|
||||
}
|
||||
free((void*)masks);
|
||||
return;
|
||||
}
|
||||
|
||||
int getConfigData(FILE* file, wchar_t* p) {
|
||||
addLogMessage(L"[ЧЕКПОИНТ] Я в getConfigData", 0);
|
||||
|
||||
wchar_t buffer[MMAX_PATH] = { 0 };
|
||||
if (fgetws(buffer, MMAX_PATH, file) != NULL) {
|
||||
|
||||
addLogMessage(L"[ЧЕКПОИНТ] getConfigData", 0);
|
||||
addLogMessage(buffer, 0);
|
||||
addLogMessage(L"\n", 0);
|
||||
|
||||
wchar_t* t = wcschr(buffer, L' ');
|
||||
if (t) {
|
||||
wcscpy_s(p, MMAX_PATH, t + 1);
|
||||
size_t len = wcslen(p);
|
||||
if (len > 0 && p[len - 1] == L'\n') {
|
||||
p[len - 1] = L'\0';
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
addLogMessage(L"[ОШИБКА] fgetws(buffer, MMAX_PATH, file) == NULL", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Функция для чтения конфигурационного файла
|
||||
int readConfig(
|
||||
const wchar_t* filename,
|
||||
wchar_t* zipDirPath,
|
||||
wchar_t* zipDirArchive,
|
||||
wchar_t** masks) {
|
||||
|
||||
FILE* file;
|
||||
_wfopen_s(&file, filename, L"r, ccs=UTF-8"); // Используем кодировку UTF-8 чтобы русские слова корректно читать
|
||||
if (file == NULL) {
|
||||
return 0; // Изменено на false
|
||||
}
|
||||
|
||||
getConfigData(file, zipDirPath);
|
||||
getConfigData(file, zipDirArchive);
|
||||
|
||||
int i = -1;
|
||||
while (getConfigData(file, masks[++i]) && (i < MMAX_ROWS)); // маски считываем пока они есть. построчно
|
||||
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
//
|
||||
// бэкап директории по пути к ней
|
||||
void zipDir(wchar_t* zipDirPath, wchar_t* zipDirArchive, wchar_t** masks) {
|
||||
// Создаем ZIP-архив
|
||||
addLogMessage(L"zashel v zipDir", 228);
|
||||
char cArchivePath[MMAX_PATH] = { 0 };
|
||||
|
||||
wchar_t archivePath[MMAX_PATH] = { 0 };
|
||||
|
||||
// формирование полного пути к архиву
|
||||
addLogMessage(L"Перед формированием пути", 228);
|
||||
errno_t err;
|
||||
addLogMessage(zipDirArchive, 1);
|
||||
err = wcsncpy_s(archivePath, MMAX_PATH, zipDirArchive, wcslen(zipDirArchive));
|
||||
if (err != 0) {
|
||||
// Обработка ошибки
|
||||
addLogMessage(L"wcsncpy_s error:", err);
|
||||
return; // или exit(1);
|
||||
}
|
||||
|
||||
err = wcscat_s(archivePath, MMAX_PATH, wcsrchr(zipDirPath, TEXT('/')));
|
||||
if (err != 0) {
|
||||
// Обработка ошибки
|
||||
addLogMessage(L"wcscat_s error:", err);
|
||||
return; // или exit(1);
|
||||
}
|
||||
|
||||
err = wcscat_s(archivePath, MMAX_PATH, L".zip");
|
||||
if (err != 0) {
|
||||
// Обработка ошибки
|
||||
addLogMessage(L"wcscat_s error:", err);
|
||||
return; // или exit(1);
|
||||
}
|
||||
|
||||
addLogMessage(L"sformiroval put", 228);
|
||||
|
||||
//_tprintf(TEXT("[ПОЛНЫЙ ПУТЬ К АРХИВУ] %s\n"), archivePath);
|
||||
WideCharToMultiByte(CP_UTF8, 0, archivePath, -1, cArchivePath, MMAX_PATH, NULL, NULL);
|
||||
|
||||
// Проверка на существование архива
|
||||
zip_t* zipArchive = zip_open(cArchivePath, ZIP_CREATE | ZIP_TRUNCATE, &err);
|
||||
if (!zipArchive) {
|
||||
zipArchive = zip_open(cArchivePath, ZIP_TRUNCATE, &err);
|
||||
if (!zipArchive) {
|
||||
//_tprintf(L"Не удалось открыть ZIP файл\n");
|
||||
addLogMessage(L"ne udalos sozdat zipfile", 228);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addLogMessage(L"zipfile sozdan, zahodim v _zipDir", 228);
|
||||
//_tprintf(L"ZIP файл был создан по пути: %s\n", archivePath);
|
||||
_zipDir(zipDirPath, zipArchive, zipDirPath, masks);
|
||||
zip_close(zipArchive);
|
||||
}
|
||||
|
||||
void _zipDir(wchar_t* directory,
|
||||
zip_t* zipArchive,
|
||||
wchar_t* baseDirectory,
|
||||
wchar_t** masks) {
|
||||
|
||||
WIN32_FIND_DATA FindFileData;
|
||||
HANDLE hFind;
|
||||
|
||||
// Формируем строку для поиска файлов и директорий
|
||||
wchar_t searchPath[MMAX_PATH] = { 0 };
|
||||
//_snwprintf_s(searchPath, MMAX_PATH, TEXT("%s/*.*"), directory);
|
||||
wcscat_s(searchPath, MMAX_PATH, directory);
|
||||
wcscat_s(searchPath, MMAX_PATH, L"/*.*");
|
||||
//_tprintf(L"searchPath = %s\n", searchPath);
|
||||
|
||||
hFind = FindFirstFile(searchPath, &FindFileData);
|
||||
if (hFind == INVALID_HANDLE_VALUE) {
|
||||
//printf("FindFirstFile вернулось с ошибкой: (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
else {
|
||||
do {
|
||||
// Игнорируем папки "." и ".."
|
||||
if (_tcscmp(FindFileData.cFileName, TEXT(".")) != 0 &&
|
||||
_tcscmp(FindFileData.cFileName, TEXT("..")) != 0)
|
||||
{
|
||||
wchar_t filePath[MMAX_PATH] = { 0 };
|
||||
//_snwprintf_s(filePath, MMAX_PATH, TEXT("%s/%s"), directory, FindFileData.cFileName);
|
||||
|
||||
wcscat_s(filePath, MMAX_PATH, directory);
|
||||
wcscat_s(filePath, MMAX_PATH, L"/");
|
||||
wcscat_s(filePath, MMAX_PATH, FindFileData.cFileName);
|
||||
|
||||
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
//_tprintf(TEXT("[ДИРЕКТОРИЯ] %s\n"), FindFileData.cFileName);
|
||||
|
||||
// Рекурсивный вызов для поддиректории
|
||||
_zipDir(filePath, zipArchive, baseDirectory, masks);
|
||||
}
|
||||
else {
|
||||
//_tprintf(TEXT("[ФАЙЛ] %s\n"), FindFileData.cFileName);
|
||||
|
||||
// проверка на совпадения по маскам
|
||||
for (int i = 0; i < MMAX_ROWS; ++i) {
|
||||
if (masks[i][0] && isMatch(FindFileData.cFileName, masks[i])) { // есть совпадение с i-й маской
|
||||
break; // выходим из цикла во внешний код
|
||||
}
|
||||
// иначе если дальше нет масок
|
||||
// (нулевый первый символ маски означает что там память, заполненная нулями)
|
||||
else if (!masks[i][0]) {
|
||||
goto CONTINUE; // continue для внешнего цикла do
|
||||
}
|
||||
// иначе смотрим след маску
|
||||
}
|
||||
|
||||
char cfilePath[MMAX_PATH], cfileName[MMAX_PATH];
|
||||
|
||||
// Преобразую юникодный путь в обычный байтовый, т.к. libzip`ные ф-ии требуют char*
|
||||
WideCharToMultiByte(CP_UTF8, 0, filePath, -1, cfilePath, MMAX_PATH, NULL, NULL);
|
||||
|
||||
// Получаем относительный путь для сохранения структуры директории в архиве
|
||||
wchar_t relativePath[MMAX_PATH];
|
||||
_snwprintf_s(relativePath, MMAX_PATH, TEXT("%s"), filePath + wcslen(baseDirectory) + 1);
|
||||
|
||||
WideCharToMultiByte(CP_UTF8, 0, relativePath, -1, cfileName, MMAX_PATH, NULL, NULL);
|
||||
|
||||
// Добавляем файл в архив
|
||||
zip_source_t* source = zip_source_file(zipArchive, cfilePath, 0, 0);
|
||||
if (source == NULL || zip_file_add(zipArchive, cfileName, source, ZIP_FL_OVERWRITE) < 0) {
|
||||
//printf("Ошибка добавления файла %ls в архив: %s\n", filePath, zip_strerror(zipArchive));
|
||||
if (source) {
|
||||
zip_source_free(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CONTINUE:;
|
||||
} while (FindNextFile(hFind, &FindFileData) != 0);
|
||||
|
||||
FindClose(hFind);
|
||||
}
|
||||
}
|
||||
|
||||
void consoleEncodingNormalize(void) {
|
||||
// ОС вернет свою кодировку и она будет установлена для консоли
|
||||
setlocale(LC_ALL, "ru_RU.UTF-8");
|
||||
}
|
||||
|
||||
int isMatch(wchar_t* str, wchar_t* pattern) {
|
||||
const wchar_t* s = str;
|
||||
const wchar_t* p = pattern;
|
||||
const wchar_t* star = NULL;
|
||||
const wchar_t* ss = NULL;
|
||||
|
||||
while (*s) {
|
||||
if (*p == '?' || *p == *s) {
|
||||
// Символ совпадает или паттерн - ?
|
||||
s++;
|
||||
p++;
|
||||
}
|
||||
else if (*p == '*') {
|
||||
// Найден * в паттерне
|
||||
star = p++;
|
||||
ss = s;
|
||||
}
|
||||
else if (star) {
|
||||
// Предыдущий * совпадает с одним символом
|
||||
p = star + 1;
|
||||
s = ++ss;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка оставшихся символов в паттерне
|
||||
while (*p == '*') {
|
||||
p++;
|
||||
}
|
||||
|
||||
return *p == '\0';
|
||||
}
|
||||
//
|
||||
|
||||
|
||||
int installService() {
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_CREATE_SERVICE);
|
||||
if (!hSCManager) {
|
||||
addLogMessage(L"Error: Can't open Service Control Manager", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
SC_HANDLE hService = CreateService(
|
||||
hSCManager,
|
||||
serviceName,
|
||||
serviceName,
|
||||
SERVICE_ALL_ACCESS,
|
||||
SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL,
|
||||
servicePath,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
);
|
||||
if (!hService) {
|
||||
int err = GetLastError();
|
||||
switch (err) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
addLogMessage(L"Error: ERROR_ACCESS_DENIED", err);
|
||||
break;
|
||||
case ERROR_CIRCULAR_DEPENDENCY:
|
||||
addLogMessage(L"Error: ERROR_CIRCULAR_DEPENDENCY", err);
|
||||
break;
|
||||
case ERROR_DUPLICATE_SERVICE_NAME:
|
||||
addLogMessage(L"Error: ERROR_DUPLICATE_SERVICE_NAME", err);
|
||||
break;
|
||||
case ERROR_INVALID_HANDLE:
|
||||
addLogMessage(L"Error: ERROR_INVALID_HANDLE", err);
|
||||
break;
|
||||
case ERROR_INVALID_NAME:
|
||||
addLogMessage(L"Error: ERROR_INVALID_NAME", err);
|
||||
break;
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
addLogMessage(L"Error: ERROR_INVALID_PARAMETER", err);
|
||||
break;
|
||||
case ERROR_INVALID_SERVICE_ACCOUNT:
|
||||
addLogMessage(L"Error: ERROR_INVALID_SERVICE_ACCOUNT", err);
|
||||
break;
|
||||
case ERROR_SERVICE_EXISTS:
|
||||
addLogMessage(L"Error: ERROR_SERVICE_EXISTS", err);
|
||||
break;
|
||||
default:
|
||||
addLogMessage(L"Error: Undefined", err);
|
||||
}
|
||||
CloseServiceHandle(hSCManager);
|
||||
return -1;
|
||||
}
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
addLogMessage(L"Success install service!", 0);
|
||||
return 0;
|
||||
}
|
||||
int removeService() {
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (!hSCManager) {
|
||||
addLogMessage(L"Error: Can't open Service Control Manager", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
SC_HANDLE hService = OpenService(hSCManager, serviceName,
|
||||
SERVICE_STOP | DELETE);
|
||||
if (!hService) {
|
||||
addLogMessage(L"Error: Can't remove service", GetLastError());
|
||||
CloseServiceHandle(hSCManager);
|
||||
return -1;
|
||||
}
|
||||
DeleteService(hService);
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
addLogMessage(L"Success remove service!", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int startService() {
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||
SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_START);
|
||||
if (!StartService(hService, 0, NULL)) {
|
||||
CloseServiceHandle(hSCManager);
|
||||
addLogMessage(L"Error: Can't start service", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stopService() {
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||
SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_STOP);
|
||||
SERVICE_STATUS ss;
|
||||
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &ss))
|
||||
{
|
||||
CloseServiceHandle(hSCManager);
|
||||
addLogMessage(L"Невозможно остановить сервис", 0);
|
||||
return -1;
|
||||
}
|
||||
addLogMessage(L"Сервис успешно остановлен", 0);
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int initService() {
|
||||
int result = addLogMessage(L"Service started!", 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ControlHandler(DWORD request) {
|
||||
switch (request)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP:
|
||||
addLogMessage(L"Stopped.", 0);
|
||||
serviceStatus.dwWin32ExitCode = 0;
|
||||
serviceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
SetServiceStatus(hStatus, &serviceStatus);
|
||||
return;
|
||||
case SERVICE_CONTROL_SHUTDOWN:
|
||||
addLogMessage(L"Shutdown.", 0);
|
||||
serviceStatus.dwWin32ExitCode = 0;
|
||||
serviceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
SetServiceStatus(hStatus, &serviceStatus);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
SetServiceStatus(hStatus, &serviceStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
// только на английском сообщения сюда передавать
|
||||
int addLogMessage(wchar_t* str, int code) {
|
||||
errno_t err;
|
||||
FILE* log;
|
||||
|
||||
// Открываем файл для добавления (append) с кодировкой UTF-8
|
||||
if ((err = _wfopen_s(&log, logfileName, L"a, ccs=UTF-8")) != 0) {
|
||||
return -1; // Ошибка при открытии файла
|
||||
}
|
||||
|
||||
if (log == NULL) return -1;
|
||||
|
||||
// Записываем сообщение в лог
|
||||
fwprintf(log, L"[code: %d] %s\n", code, str); // Используем fwprintf для записи wstring
|
||||
fclose(log);
|
||||
return 0; // Успешное выполнение
|
||||
}
|
Loading…
Reference in New Issue