#include "Utility.h" #include #include #include #include // Per-path mutex to serialize concurrent zip operations on the same target. // Without this, two LabVIEW threads can race: one extracting a zip while // another truncates/writes the same file, corrupting data and crashing LabVIEW. // Also used to serialize extract ↔ ONNX session creation on the same extracted // model folder — without that, thread A can finish extraction and begin opening // train_last.onnx while thread B re-enters extraction and truncates the file, // producing "system error number 13" (EACCES) on the first reader. // Recursive so the same thread can re-acquire the lock through layered load // calls — ANSALPR_OD::LoadEngine -> ANSONNXYOLO::LoadModelFromFolder both // acquire the SAME folder lock on the SAME thread. A non-recursive // timed_mutex deadlocks that nesting for 120 s then fails. Recursive keeps // cross-thread serialization intact while allowing legitimate re-entry from // the lock-holding thread. static std::mutex g_zipPathMapMutex; static std::map> g_zipPathLocks; static std::shared_ptr GetZipPathLock(const std::string& path) { std::lock_guard guard(g_zipPathMapMutex); auto& ptr = g_zipPathLocks[path]; if (!ptr) ptr = std::make_shared(); return ptr; } std::shared_ptr GetModelFolderLock(const std::string& folderPath) { auto ptr = GetZipPathLock(folderPath); ANS_DBG("ModelLock", "GetModelFolderLock: folder=%s mutex=%p", folderPath.c_str(), (void*)ptr.get()); return ptr; } template T get_data(const boost::property_tree::ptree& pt, const std::string& key) { T ret; if (boost::optional data = pt.get_optional(key)) { ret = data.get(); } else { return T(); //throw std::runtime_error("Could not read the data from ptree: [key: " + key + "]"); } return ret; } // Convert file path to only the filename std::string path_to_filename(std::string path) { return path.substr(path.find_last_of("/\\") + 1); } std::string GetMainFolderDir() { std::string loggerDir = "C:\\ProgramData\\ANSCENTER"; if (!FolderExist(loggerDir)) { CreateDirectory(loggerDir); } return loggerDir; } std::string GetLoggerDir() { std::string mainFolderDir = GetMainFolderDir(); #ifdef _WIN32 const char pathSeparator = '\\'; // Windows uses backslash #else const char pathSeparator = '/'; // Unix-like systems use forward slash #endif std::string loggerDir = mainFolderDir+ pathSeparator+"Logs"; if (!FolderExist(loggerDir)) { CreateDirectory(loggerDir); } return loggerDir; } std::string GetLicenseDir() { std::string mainFolderDir = GetMainFolderDir(); #ifdef _WIN32 const char pathSeparator = '\\'; // Windows uses backslash #else const char pathSeparator = '/'; // Unix-like systems use forward slash #endif std::string loggerDir = mainFolderDir + pathSeparator + "Licenses"; if (!FolderExist(loggerDir)) { CreateDirectory(loggerDir); } return loggerDir; } std::wstring String2WString(const std::string input) { auto w = fs::path(input).wstring(); return w; } std::string WString2String(const std::wstring input) { auto s = fs::path(input).string(); return s; } std::string Dash2Underscore(std::string text) { std::replace(text.begin(), text.end(), '-', '_'); return text; } std::string Underscore2Dash(std::string text) { std::replace(text.begin(), text.end(), '_', '-'); return text; } std::string Space2Underscore(std::string text) { std::replace(text.begin(), text.end(), ' ', '_'); return text; } bool FileExist(const std::string& filePath) { return fs::exists(filePath) && fs::is_regular_file(filePath); } bool FolderExist(const std::string& folderPath) { return fs::is_directory(folderPath); } bool CreateDirectory(const std::string& folderPath) { try { fs::create_directory(folderPath); return true; } catch (const std::exception& ex) { std::cerr << "Creating directory: " << ex.what() << std::endl; return false; } } bool RenameFile(const std::string& oldFilePath, const std::string& newFilePath) { try { fs::rename(oldFilePath, newFilePath); return true; } catch (const std::exception& ex) { std::cerr << "Error renaming file: " << ex.what() << std::endl; return false; } } bool CopyFile(const std::string& sourceFilePath, const std::string& destinationFilePath) { try { fs::copy_file(sourceFilePath, destinationFilePath, fs::copy_options::overwrite_existing); return true; } catch (const std::exception& ex) { std::cerr << "Error copying file: " << ex.what() << std::endl; return false; } } void CopyFiles(const std::string& sourceFolder, const std::string& destinationFolder) { for (const auto& entry : std::filesystem::directory_iterator(sourceFolder)) { if (entry.is_regular_file()) { std::filesystem::copy(entry.path(), destinationFolder / entry.path().filename()); } } } bool DeleteFile(const std::string& filePath) { try { fs::remove(filePath); return true; } catch (const std::exception& ex) { std::cerr << "Error deleting file: " << ex.what() << std::endl; return false; } } std::string FindFilePathInFileList(const std::vector& vec, const std::string& substring) { auto it = std::find_if(vec.begin(), vec.end(), [&substring](const std::string& str) { return str.find(substring) != std::string::npos; }); return (it != vec.end()) ? *it : ""; } bool DeleteLicenseFile(const std::string& filePath) { try { fs::remove(filePath); return true; } catch (const std::exception& ex) { std::cerr << "Error deleting file: " << ex.what() << std::endl; return false; } } void ToLowerCase(std::string& str) { for (auto& c : str) { c = tolower(c); } } std::string toLower(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; } std::string GetFileExtension(const std::string& filePath) { fs::path path(filePath); std::string extensionFile= path.extension().string(); ToLowerCase(extensionFile); return extensionFile; } std::vector ListFilesInFolder(const std::string& folderPath) { std::vector filePaths; try { for (const auto& entry : std::filesystem::directory_iterator(folderPath)) { if (entry.is_regular_file()) { std::string filePath = entry.path().string(); std::string fileName = GetFileNameWithoutExtension(filePath); std::string _licenseKey, _activationKey, _validationData; if (ReadLicenseKeyFile(filePath, _licenseKey, _activationKey, _validationData)) { filePaths.push_back(entry.path().string()); } } } } catch (const std::exception& ex) { std::cerr << "Error listing files in folder: " << ex.what() << std::endl; } return filePaths; } void DeleteFilesInFolderExcept(const std::string& folderPath, const std::vector& filesToKeep) { try { for (const auto& entry : fs::directory_iterator(folderPath)) { if (entry.is_regular_file()) { std::string fileName = entry.path().filename().string(); if (std::find(filesToKeep.begin(), filesToKeep.end(), fileName) == filesToKeep.end()) { // File is not in the list of files to keep; delete it. fs::remove(entry.path()); std::cout << "Deleted: " << entry.path() << std::endl; } } } } catch (const std::exception& ex) { std::cerr << "Error deleting files in folder: " << ex.what() << std::endl; } } bool DeleteFolder(const std::string& folderPath) { try { fs::remove_all(folderPath); return true; } catch (const std::exception& ex) { std::cerr << "Error deleting folder: " << ex.what() << std::endl; return false; } } std::string GetFileNameWithoutExtension(const std::string& filePath) { fs::path path(filePath); return path.stem().string(); } std::string ReadFileContent(std::string filePath) { std::ifstream ifs(filePath); return std::string((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); } std::string ReadFileAsBinary(const std::string& filename) { std::ifstream file(filename, std::ios::binary); if (!file) { std::cerr << "Failed to open file: " << filename << std::endl; return ""; // Return an empty string to indicate failure } // Read the file content into a stringstream std::stringstream buffer; buffer << file.rdbuf(); // Convert the stringstream to a string return buffer.str(); } int ReadAllBytes(std::string filePath, std::vector& allDataBytes) { // Open the file in binary mode std::ifstream file(filePath, std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file." << std::endl; return 1; } // Get the file size file.seekg(0, std::ios::end); std::streampos fileSize = file.tellg(); file.seekg(0, std::ios::beg); // Create a vector to store the file data std::vector fileData(fileSize); // Read the file into the vector if (!file.read(fileData.data(), fileSize)) { std::cerr << "Failed to read file." << std::endl; return 1; } allDataBytes = fileData; return 0; } // Functionto get parent folder path (C:\ParentFolder\FileName) -> C:\ParentFolder std::string GetParentFolder(const std::string& filePath) { size_t lastSlashPos = filePath.find_last_of("/\\"); // Handle both '/' and '\' if (lastSlashPos != std::string::npos) { return filePath.substr(0, lastSlashPos); } return ""; // Return an empty string if there is no folder } // Function to extract the folder name from a file path or file name std::string ExtractFolderName(const std::string& directoryPath) { std::filesystem::path path(directoryPath); return path.stem().string(); } // Function to create a file path from a folder name and a file name std::string CreateFilePath(const std::string& folderName, const std::string& fileName) { #ifdef _WIN32 const char pathSeparator = '\\'; // Windows uses backslash #else const char pathSeparator = '/'; // Unix-like systems use forward slash #endif std::string folder = folderName; if (!folder.empty() && folder.back() != pathSeparator) { folder += pathSeparator; } return folder + fileName; } std::filesystem::path GetSafeTempDirectory() { std::filesystem::path systemTemp = std::filesystem::temp_directory_path(); std::filesystem::path fallbackTemp = "C:\\ProgramData\\Jh7O7nUe7vS\\"; // Ensure fallback directory exists if (!std::filesystem::exists(fallbackTemp)) { try { std::filesystem::create_directories(fallbackTemp); } catch (const std::exception& e) { std::cerr << "Failed to create fallback temp directory: " << e.what() << std::endl; } } // Convert systemTemp to lowercase string for checking std::string systemTempStr = toLower(systemTemp.string()); // Check if systemTemp does not exist or contains "c:\windows" if (!std::filesystem::exists(systemTemp) || systemTempStr.find("windows") != std::string::npos) { return fallbackTemp; } return systemTemp; } std::string GenerateOutputFolder(std::string modelFilePath, std::string modelName, bool edgeDeviceModel) { // Get the path to the Temp folder std::filesystem::path tempFolderPath = GetSafeTempDirectory(); std::string outputFolder = ""; outputFolder.clear(); if (std::filesystem::exists(tempFolderPath)) { std::string strTempPath = tempFolderPath.string(); #ifdef _WIN32 const char pathSeparator = '\\'; // Windows uses backslash #else const char pathSeparator = '/'; // Unix-like systems use forward slash #endif std::string baseFolder = strTempPath + "Models"; if(!FolderExist(baseFolder)) { CreateDirectory(baseFolder); } std::string EdgeModelsFolder = baseFolder + pathSeparator + "EdgeModels"; if (!FolderExist(EdgeModelsFolder)) { CreateDirectory(EdgeModelsFolder); } std::string EngineModelsFolder = baseFolder + pathSeparator + "EngineModels"; if (!FolderExist(EngineModelsFolder)) { CreateDirectory(EngineModelsFolder); } if(edgeDeviceModel) outputFolder = EdgeModelsFolder + pathSeparator + modelName; else outputFolder = EngineModelsFolder + pathSeparator + modelName; return outputFolder; } else { return outputFolder; } } bool ExtractPasswordProtectedZip(const std::string& zipFileName, const std::string& password, const std::string& modelName, std::string& outputFolder, bool edgeDeviceModel) { outputFolder = GenerateOutputFolder(zipFileName, modelName, edgeDeviceModel); return ExtractProtectedZipFile(zipFileName, password, modelName, outputFolder); } bool AddFileToZip(zip* archive, const char* filePath, const char* zipPath, const char* password) { struct zip_source* source = zip_source_file(archive, filePath, 0, 0); if (!source) { std::cerr << "Failed to add file '" << filePath << "' to ZIP archive." << std::endl; return false; } if (zip_file_add(archive, zipPath, source, ZIP_FL_OVERWRITE) == -1) { std::cerr << "Failed to add file '" << filePath << "' to ZIP archive." << std::endl; zip_source_free(source); return false; } // Get the index of the added file zip_int64_t fileIndex = zip_name_locate(archive, zipPath, 0); if (fileIndex < 0) { std::cerr << "Failed to locate file '" << zipPath << "' in ZIP archive." << std::endl; zip_source_free(source); return false; } // Set encryption for the added file if (zip_file_set_encryption(archive, fileIndex, ZIP_EM_AES_256, password) == -1) { std::cerr << "Failed to set encryption for file '" << zipPath << "' in ZIP archive." << std::endl; zip_source_free(source); return false; } return true; } bool AddFolderContentsToZip(zip* archive, const char* folderPath, const char* zipPath, const char* password) { try { fs::path dir(folderPath); if (!fs::exists(dir) || !fs::is_directory(dir)) { std::cerr << "Failed to open directory '" << folderPath << "'." << std::endl; return false; } for (const fs::directory_entry& entry : fs::directory_iterator(dir)) { std::string entryName = entry.path().filename().string(); std::string entryPath = (zipPath && zipPath[0] != '\0') ? std::string(zipPath) + "/" + entryName : entryName; std::string filePath = folderPath + std::string("/") + entryName; if (fs::is_directory(entry.status())) { if (entryName != "." && entryName != "..") { if (!AddFolderContentsToZip(archive, filePath.c_str(), entryPath.c_str(), password)) { return false; } } } else { if (!AddFileToZip(archive, filePath.c_str(), entryPath.c_str(), password)) { return false; } } } } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; return false; } return true; } bool ZipFolderWithPassword(const char* folderPath, const char* zipFilePath, const char* password) { auto pathLock = GetZipPathLock(std::string(zipFilePath)); std::lock_guard zipGuard(*pathLock); zip* zipArchive; zip_flags_t flags = ZIP_CREATE | ZIP_TRUNCATE; //std::cout << "Open Zip File: " << zipFilePath << std::endl; zipArchive = zip_open(zipFilePath, flags, nullptr); if (!zipArchive) { std::cerr << "Failed to create ZIP archive." << std::endl; return false; } // Set a password for the ZIP file if (zip_set_default_password(zipArchive, password) == -1) { std::cerr << "Failed to set the password for the ZIP file." << std::endl; zip_close(zipArchive); return false; } // Add folder contents to the ZIP archive if (!AddFolderContentsToZip(zipArchive, folderPath, "", password)) { zip_close(zipArchive); return false; } // Save the ZIP archive to the file if (zip_close(zipArchive) == -1) { std::cerr << "Failed to save the ZIP archive to the file." << std::endl; return false; } return true; } std::string VectorToCommaSeparatedString(const std::vector& inputVector) { std::string result; for (size_t i = 0; i < inputVector.size(); ++i) { result += inputVector[i]; if (i < inputVector.size() - 1) { result += ","; } } return result; } std::string VectorToSeparatedString(const std::vector& inputVector) { std::string result; for (size_t i = 0; i < inputVector.size(); ++i) { result += inputVector[i]; if (i < inputVector.size() - 1) { result += ";"; } } return result; } std::vector Split(const std::string str, const std::string regex_str) { std::regex regexz(regex_str); return { std::sregex_token_iterator(str.begin(), str.end(), regexz, -1), std::sregex_token_iterator() }; } std::vector GetTrialLicenseKeyFiles(std::string registrationName) { std::vector < std::string> trialLicenseKeyFiles; std::string trialLicenseKeyFileName = registrationName + "_Trial.lic"; if (CreateDirectory("C:\\ProgramData\\Apple")) { std::string trialLicenseKeyFile1 = CreateFilePath("C:\\ProgramData\\Apple", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile1); } if (CreateDirectory("C:\\ProgramData\\Licenses")) { std::string trialLicenseKeyFile2 = CreateFilePath("C:\\ProgramData\\Licenses", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile2); } if (CreateDirectory("C:\\ProgramData\\McAfee")) { std::string trialLicenseKeyFile3 = CreateFilePath("C:\\ProgramData\\McAfee", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile3); } if (CreateDirectory("C:\\ProgramData\\NVIDIA")) { std::string trialLicenseKeyFile4 = CreateFilePath("C:\\ProgramData\\NVIDIA", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile4); } if (CreateDirectory("C:\\ProgramData\\National Instruments")) { std::string trialLicenseKeyFile5 = CreateFilePath("C:\\ProgramData\\National Instruments", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile5); } if (CreateDirectory("C:\\ProgramData\\Microsoft")) { std::string trialLicenseKeyFile6 = CreateFilePath("C:\\ProgramData\\Microsoft", trialLicenseKeyFileName); trialLicenseKeyFiles.push_back(trialLicenseKeyFile6); } return trialLicenseKeyFiles; } bool ReadLicenseKeyFile(std::string licenseKeyFilePath, std::string& licenseKey, std::string& activationKey, std::string& validationData) { try { if (!FileExist(licenseKeyFilePath))return false; boost::property_tree::ptree pt; boost::property_tree::read_json(licenseKeyFilePath, pt); auto itLicenseKey = pt.get_child("license_key"); licenseKey = itLicenseKey.get_value(); auto itActivationKey = pt.get_child("activation_key"); activationKey = itActivationKey.get_value(); auto itRegistrationName = pt.get_child("license_key_validation_data"); validationData = itRegistrationName.get_value(); if ((licenseKey.empty()) || (activationKey.empty()) || (validationData.empty())) return false; return true; } catch (std::exception& e) { std::cout<<"Read License key"<< e.what()<< std::endl; return false; } } std::string GetTrialLicenseKey(std::string registrationName) { std::string trialLicenseKey; std::vector trialLicenseKeys; trialLicenseKeys.clear(); // Get all licenseKeys from vavious places std::vector < std::string> trialLicenseKeyFiles =GetTrialLicenseKeyFiles(registrationName); size_t trialSize = trialLicenseKeyFiles.size(); for (size_t i = 0; i < trialSize; i++) { std::string trialLicenseFile = trialLicenseKeyFiles[i]; if (FileExist(trialLicenseFile)) { std::string trialLicenseKey = ReadFileContent(trialLicenseFile); trialLicenseKeys.push_back(trialLicenseKey); } } sort(trialLicenseKeys.begin(), trialLicenseKeys.end()); trialLicenseKeys.erase(unique(trialLicenseKeys.begin(), trialLicenseKeys.end()), trialLicenseKeys.end()); size_t lSize = trialLicenseKeys.size(); if (lSize == 1) trialLicenseKey = trialLicenseKeys[0]; return trialLicenseKey; } bool UpdateTrialLicenseKey(std::string trialLicenseKey, std::string registrationName) { try { std::vector < std::string> trialLicenseKeyFiles = GetTrialLicenseKeyFiles(registrationName); size_t trialSize = trialLicenseKeyFiles.size(); for (size_t i = 0; i < trialSize; i++) { try { std::string trialLicenseFile = trialLicenseKeyFiles[i]; std::ofstream(trialLicenseFile) << trialLicenseKey; } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; } } return true; } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; return false; } } bool RemoveTrialLicenseKey(std::string registrationName) { try { std::vector < std::string> trialLicenseKeyFiles = GetTrialLicenseKeyFiles(registrationName); size_t trialSize = trialLicenseKeyFiles.size(); for (size_t i = 0; i < trialSize; i++) { try { DeleteFile(trialLicenseKeyFiles[i]); } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; } } return true; } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; return false; } } int GetExpirationDate(std::string licenseData, int &productId) { if (licenseData.empty()) return -1; int remainDate = -1; std::stringstream ss; ss << licenseData; boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); auto rootNode = pt.get_child("licenseData"); auto childNode = rootNode.get_child("remaining_days"); remainDate = childNode.get_value(); childNode = rootNode.get_child("valid_product_id"); productId = childNode.get_value(); return remainDate; } std::string GetStringCurrentDateTime() { // Get the current time std::time_t t = std::time(nullptr); std::tm now = {}; // Initialize to all zeros localtime_s(&now, &t); // Convert the time to a string std::stringstream ss; ss << std::put_time(&now, "%Y%m%d%H%M%S"); std::string currentDateTime = ss.str(); return currentDateTime; } std::string GetDateTimeString(const std::string& format) { // Get the current time std::time_t t = std::time(nullptr); std::tm now = {}; // Initialize to all zeros localtime_s(&now, &t); // Convert the time to a string std::stringstream ss; ss << std::put_time(&now, format.c_str()); std::string currentDateTime = ss.str(); return currentDateTime; } // For traing engine //bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder) //{ // int error; // if (!FileExist(zipFileName))return false; // zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error); // if (!archive) { // std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl; // return false; // } // try { // if (!password.empty()) { // if (zip_set_default_password(archive, password.c_str()) == -1) { // std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl; // zip_close(archive); // return false; // } // } // int numEntries = (int)zip_get_num_entries(archive, 0); // for (int i = 0; i < numEntries; i++) { // zip_file_t* file = zip_fopen_index(archive, i, 0); // if (!file) { // std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl; // zip_close(archive); // return false; // } // // Check if the folder exists // if (!std::filesystem::exists(outputFolder)) { // // Folder does not exist, so create it // try { // std::filesystem::create_directories(outputFolder); // } // catch (const std::exception& ex) { // std::cerr << "Error opening file in ZIP: " << ex.what() << std::endl; // zip_close(archive); // zip_fclose(file); // return false; // } // } // try {// otherwise extract to the output folder // const char* filename = zip_get_name(archive, i, 0); // std::string outputPath = CreateFilePath(outputFolder, filename); // // // Check if filename ends with '/' indicating it's a directory // if (outputPath.back() == '/') {// Create the directory and its parents as needed // fs::create_directories(outputPath); // } // else {// Proceed to extract the file // std::ofstream output(outputPath, std::ios::binary); // if (!output.is_open()) { // zip_fclose(file); // } // else { // const size_t buffer_size = 4096; // unsigned char buffer[buffer_size]; // zip_int64_t bytesRead; // while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) { // output.write(reinterpret_cast(buffer), bytesRead); // } // zip_fclose(file); // output.close(); // } // } // } // catch (const std::exception& ex) { // std::cerr << "Error creating output file: " << ex.what() << std::endl; // } // } // } // catch (...) { // zip_close(archive); // return false; // } // zip_close(archive); // return true; //} //bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder) { // int error; // if (!FileExist(zipFileName)) return false; // // zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error); // if (!archive) { // std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl; // return false; // } // // try { // if (!password.empty()) { // if (zip_set_default_password(archive, password.c_str()) == -1) { // std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl; // zip_close(archive); // return false; // } // } // // int numEntries = static_cast(zip_get_num_entries(archive, 0)); // for (int i = 0; i < numEntries; i++) { // zip_file_t* file = zip_fopen_index(archive, i, 0); // if (!file) { // std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl; // zip_close(archive); // return false; // } // // // Check and create the output folder if necessary // if (!std::filesystem::exists(outputFolder)) { // try { // std::filesystem::create_directories(outputFolder); // } // catch (const std::exception& ex) { // std::cerr << "Error creating output directory: " << ex.what() << std::endl; // zip_fclose(file); // zip_close(archive); // return false; // } // } // // try { // const char* filename = zip_get_name(archive, i, 0); // std::string outputPath = CreateFilePath(outputFolder, filename); // // // Check if the path is a directory and create it if necessary // if (outputPath.back() == '/') { // std::filesystem::create_directories(outputPath); // } // else { // std::ofstream output(outputPath, std::ios::binary); // if (!output.is_open()) { // std::cerr << "Error creating output file: " << outputPath << std::endl; // zip_fclose(file); // zip_close(archive); // return false; // } // // const size_t buffer_size = 4096; // unsigned char buffer[buffer_size]; // zip_int64_t bytesRead; // while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) { // output.write(reinterpret_cast(buffer), bytesRead); // } // // output.close(); // } // // zip_fclose(file); // } // catch (const std::exception& ex) { // std::cerr << "Error creating output file: " << ex.what() << std::endl; // zip_fclose(file); // zip_close(archive); // return false; // } // } // } // catch (...) { // zip_close(archive); // return false; // } // // zip_close(archive); // return true; //} bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder) { auto pathLock = GetZipPathLock(outputFolder); std::lock_guard zipGuard(*pathLock); int error; if (!FileExist(zipFileName))return false; // Idempotent fast-path: skip re-extraction when the target folder already // holds exactly the content of the current zip. Identity is tracked by a // small sidecar file (.ans_extract_info) written inside outputFolder after // every successful extract. The sidecar records three fields — zip path, // zip size, zip mtime — and we SKIP only if ALL match the current zip AND // the folder still has at least one non-empty file. // // Why sidecar and not just mtime? mtime alone breaks on: // • downgrades — a reverted older zip has an older mtime, so "file mtime // >= zip mtime" falsely reports fresh and we never re-extract. // • preserved-timestamp copies (cp --preserve=timestamps, some installers, // Git-LFS checkouts) — new-but-older-mtime zips look "not newer". // Size differs whenever content differs (libzip's CRC/compression shifts // bytes even for tiny payload changes), so size+mtime+path uniquely pins // the zip identity in practice. // // Any mismatch / missing sidecar / FS error → fall through to full extract, // which unconditionally rewrites the sidecar at the end. This means the // first run after deploying this patched binary will do one re-extract per // model to establish the sidecar; subsequent runs hit the fast path. const std::filesystem::path sidecarPath = std::filesystem::path(outputFolder) / ".ans_extract_info"; ANS_DBG("Extract", "ExtractProtectedZipFile: zip=%s -> folder=%s", zipFileName.c_str(), outputFolder.c_str()); // Capture the current zip's identity up-front so both the fast-path check // and the post-extract sidecar write see consistent values. std::uintmax_t curZipSize = 0; long long curZipMtime = 0; bool zipStatOk = false; try { curZipSize = std::filesystem::file_size(zipFileName); curZipMtime = std::filesystem::last_write_time(zipFileName) .time_since_epoch().count(); zipStatOk = true; } catch (const std::exception& ex) { ANS_DBG("Extract", "ExtractProtectedZipFile: failed to stat zip (%s); extracting anyway", ex.what()); } try { if (zipStatOk && std::filesystem::exists(outputFolder) && std::filesystem::is_directory(outputFolder) && std::filesystem::exists(sidecarPath)) { // Read sidecar: 3 lines — zipPath, zipSize, zipMtime. std::ifstream sf(sidecarPath); std::string storedPath; std::uintmax_t storedSize = 0; long long storedMtime = 0; std::getline(sf, storedPath); sf >> storedSize >> storedMtime; if (sf.good() || sf.eof()) { // Sanity: folder must still contain at least one non-empty file // (defends against a sidecar that outlived its content, e.g. a // user manually wiping model files but leaving the sidecar). bool hasNonEmpty = false; size_t numFiles = 0; for (const auto& e : std::filesystem::directory_iterator(outputFolder)) { if (!e.is_regular_file()) continue; if (e.path().filename() == ".ans_extract_info") continue; ++numFiles; std::error_code ec; if (e.file_size(ec) > 0 && !ec) { hasNonEmpty = true; break; } } const bool pathOk = (storedPath == zipFileName); const bool sizeOk = (storedSize == curZipSize); const bool mtimeOk = (storedMtime == curZipMtime); if (pathOk && sizeOk && mtimeOk && hasNonEmpty) { ANS_DBG("Extract", "ExtractProtectedZipFile: SKIP re-extract — sidecar match (size=%llu mtime=%lld, %zu payload file(s))", (unsigned long long)curZipSize, (long long)curZipMtime, numFiles); return true; } ANS_DBG("Extract", "ExtractProtectedZipFile: full extract needed — sidecar mismatch (path:%d size:%d mtime:%d nonEmpty:%d)", pathOk ? 0 : 1, sizeOk ? 0 : 1, mtimeOk ? 0 : 1, hasNonEmpty ? 1 : 0); } else { ANS_DBG("Extract", "ExtractProtectedZipFile: sidecar unreadable, re-extracting"); } } else if (zipStatOk) { ANS_DBG("Extract", "ExtractProtectedZipFile: %s, full extract", std::filesystem::exists(outputFolder) ? "no sidecar yet" : "folder absent"); } } catch (const std::exception& ex) { // Any filesystem hiccup: fall through to full extraction (safe). ANS_DBG("Extract", "ExtractProtectedZipFile: freshness check threw, extracting: %s", ex.what()); } zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error); if (!archive) { std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl; return false; } try { if (!password.empty()) { if (zip_set_default_password(archive, password.c_str()) == -1) { std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl; zip_close(archive); return false; } } int numEntries = (int)zip_get_num_entries(archive, 0); for (int i = 0; i < numEntries; i++) { zip_file_t* file = zip_fopen_index(archive, i, 0); if (!file) { std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl; zip_close(archive); return false; } // Check if the folder exists if (!std::filesystem::exists(outputFolder)) { // Folder does not exist, so create it try { std::filesystem::create_directories(outputFolder); } catch (const std::exception& ex) { std::cerr << "Error opening file in ZIP: " << ex.what() << std::endl; zip_close(archive); zip_fclose(file); return false; } } try {// otherwise extract to the output folder const char* filename = zip_get_name(archive, i, 0); std::string outputPath = CreateFilePath(outputFolder, filename); // Check if filename ends with '/' indicating it's a directory if (outputPath.back() == '/') {// Create the directory and its parents as needed fs::create_directories(outputPath); } else {// Proceed to extract the file std::ofstream output(outputPath, std::ios::binary); if (!output.is_open()) { zip_fclose(file); } else { const size_t buffer_size = 4096; unsigned char buffer[buffer_size]; zip_int64_t bytesRead; while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) { output.write(reinterpret_cast(buffer), bytesRead); } zip_fclose(file); output.close(); } } } catch (const std::exception& ex) { std::cerr << "Error creating output file: " << ex.what() << std::endl; } } } catch (...) { zip_close(archive); return false; } zip_close(archive); // Extract succeeded — write the identity sidecar so the next call on this // folder can take the fast-path. We re-read zip stats here (rather than // trust the pre-extract snapshot) in case the zip was touched in between. try { std::uintmax_t finalSize = std::filesystem::file_size(zipFileName); long long finalMtime = std::filesystem::last_write_time(zipFileName) .time_since_epoch().count(); std::ofstream sf(sidecarPath, std::ios::binary | std::ios::trunc); if (sf.is_open()) { sf << zipFileName << '\n' << finalSize << '\n' << finalMtime << '\n'; ANS_DBG("Extract", "ExtractProtectedZipFile: sidecar written (size=%llu mtime=%lld) at %s", (unsigned long long)finalSize, (long long)finalMtime, sidecarPath.string().c_str()); } else { ANS_DBG("Extract", "ExtractProtectedZipFile: could not open sidecar for write: %s", sidecarPath.string().c_str()); } } catch (const std::exception& ex) { // Non-fatal — extract already succeeded. Next call will just re-extract // once more until the sidecar can be written. ANS_DBG("Extract", "ExtractProtectedZipFile: sidecar write threw (non-fatal): %s", ex.what()); } return true; }