#include "stdafx.h" #include "RecipeManager.h" #include #include #include #include std::recursive_mutex RecipeManager::m_mutex; RecipeManager& RecipeManager::getInstance() { static RecipeManager instance; return instance; } RecipeManager::RecipeManager() { m_pDB = new BL::SQLiteDatabase(); } RecipeManager::~RecipeManager() { if (m_pDB) { delete m_pDB; m_pDB = nullptr; } } bool RecipeManager::initRecipeTable() { char szPath[MAX_PATH]; GetModuleFileNameA(NULL, szPath, MAX_PATH); std::string exePath(szPath); std::string dbDir = exePath.substr(0, exePath.find_last_of("\\/")) + "\\DB"; CreateDirectoryA(dbDir.c_str(), NULL); std::string dbPath = dbDir + "\\RecipeManager.db"; if (!m_pDB->connect(dbPath, true)) { return false; } // ÆôÓà SQLite µÄÍâ¼üÔ¼ÊøÖ§³Ö if (!m_pDB->executeQuery("PRAGMA foreign_keys = ON;")) { std::cerr << "Failed to enable foreign keys." << std::endl; return false; } const std::string createRecipeTable = R"( CREATE TABLE IF NOT EXISTS recipes ( ppid TEXT PRIMARY KEY NOT NULL, description TEXT, create_time TEXT DEFAULT (datetime('now', 'localtime')) ); )"; const std::string createDeviceTable = R"( CREATE TABLE IF NOT EXISTS recipe_devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, ppid TEXT NOT NULL, device_id INTEGER NOT NULL, device_name TEXT NOT NULL, recipe_id INTEGER NOT NULL, FOREIGN KEY(ppid) REFERENCES recipes(ppid) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (ppid, device_id), UNIQUE (ppid, device_name) ); )"; return m_pDB->executeQuery(createRecipeTable) && m_pDB->executeQuery(createDeviceTable); } void RecipeManager::termRecipeTable() { if (!m_pDB) { return; } m_pDB->disconnect(); } bool RecipeManager::destroyRecipeTable() { if (!m_pDB) { return false; } return m_pDB->executeQuery("DROP TABLE IF EXISTS recipe_devices;") && m_pDB->executeQuery("DROP TABLE IF EXISTS recipes;"); } bool RecipeManager::ppidExists(const std::string& ppid) { std::ostringstream oss; oss << "SELECT COUNT(*) FROM recipes WHERE ppid = '" << ppid << "';"; auto result = m_pDB->fetchResults(oss.str()); return (!result.empty() && !result[0].empty() && result[0][0] != "0"); } bool RecipeManager::deviceExists(const std::string& ppid, int nDeviceID) { std::ostringstream oss; oss << "SELECT COUNT(*) FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";"; auto result = m_pDB->fetchResults(oss.str()); return (!result.empty() && !result[0].empty() && result[0][0] != "0"); } bool RecipeManager::addRecipe(const RecipeInfo& recipe) { if (!m_pDB || recipe.strPPID.empty() || recipe.vecDeviceList.empty()) { std::cerr << "[AddRecipe] Invalid input." << std::endl; return false; } std::string strTime = recipe.strCreateTime; if (strTime.empty()) { std::time_t now = std::time(nullptr); std::tm tm_now = {}; localtime_s(&tm_now, &now); std::stringstream ss; ss << std::put_time(&tm_now, "%Y-%m-%d %H:%M:%S"); strTime = ss.str(); } std::lock_guard lock(m_mutex); // ¿ªÊ¼ÊÂÎñ m_pDB->executeQuery("BEGIN TRANSACTION;"); std::ostringstream oss; oss << "INSERT OR REPLACE INTO recipes (ppid, description, create_time) VALUES ('" << recipe.strPPID << "', '" << recipe.strDescription << "', '" << strTime << "');"; if (!m_pDB->executeQuery(oss.str())) { std::cerr << "[AddRecipe] Failed to insert recipe: " << recipe.strPPID << std::endl; m_pDB->executeQuery("ROLLBACK;"); return false; } for (const auto& device : recipe.vecDeviceList) { std::ostringstream devSql; devSql << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id) VALUES ('" << recipe.strPPID << "', " << device.nDeviceID << ", '" << device.strDeviceName << "', " << device.nRecipeID << ");"; if (!m_pDB->executeQuery(devSql.str())) { std::cerr << "[AddRecipe] Failed to insert device mapping: " << device.nDeviceID << std::endl; m_pDB->executeQuery("ROLLBACK;"); return false; } } // Ìá½»ÊÂÎñ m_pDB->executeQuery("COMMIT;"); return true; } bool RecipeManager::addRecipeDevice(const std::string& ppid, const DeviceRecipe& device) { if (!m_pDB || ppid.empty() || device.nDeviceID <= 0 || device.nRecipeID <= 0) { std::cerr << "[addRecipeDevice] Invalid input." << std::endl; return false; } // ¼ì²é ppid ÊÇ·ñ´æÔÚ if (!ppidExists(ppid)) { std::cerr << "[addRecipeDevice] PPID does not exist: " << ppid << std::endl; return false; } // ²åÈëÉ豸¼Ç¼ std::ostringstream oss; oss << "INSERT OR REPLACE INTO recipe_devices (ppid, device_id, device_name, recipe_id) VALUES ('" << ppid << "', " << device.nDeviceID << ", '" << device.strDeviceName << "', " << device.nRecipeID << ");"; std::lock_guard lock(m_mutex); return m_pDB->executeQuery(oss.str()); } bool RecipeManager::deleteRecipeDeviceByID(const std::string& ppid, int nDeviceID) { if (!m_pDB || ppid.empty() || nDeviceID <= 0) { std::cerr << "[deleteRecipeDeviceByID] Invalid input." << std::endl; return false; } std::ostringstream oss; oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";"; std::lock_guard lock(m_mutex); return m_pDB->executeQuery(oss.str()); } bool RecipeManager::deleteRecipeDeviceByName(const std::string& ppid, const std::string& strDeviceName) { if (!m_pDB || ppid.empty() || strDeviceName.empty()) { std::cerr << "[deleteRecipeDeviceByName] Invalid input." << std::endl; return false; } std::ostringstream oss; oss << "DELETE FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';"; std::lock_guard lock(m_mutex); return m_pDB->executeQuery(oss.str()); } std::vector RecipeManager::getAllRecipes() { if (!m_pDB) { return {}; } std::vector recipes; auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes;"); for (const auto& row : rows) { RecipeInfo info; info.strPPID = row[0]; info.strDescription = row[1]; info.strCreateTime = row[2]; std::ostringstream devQuery; devQuery << "SELECT device_id, device_name, recipe_id FROM recipe_devices WHERE ppid = '" << info.strPPID << "';"; auto devs = m_pDB->fetchResults(devQuery.str()); for (const auto& dev : devs) { DeviceRecipe dr; dr.strPPID = info.strPPID; try { dr.nDeviceID = std::stoi(dev[0]); dr.strDeviceName = dev[1]; dr.nRecipeID = std::stoi(dev[2]); } catch (...) { std::cerr << "Invalid data in recipe_devices for PPID: " << info.strPPID << std::endl; continue; } info.vecDeviceList.push_back(dr); } recipes.push_back(info); } return recipes; } RecipeInfo RecipeManager::getRecipeByPPID(const std::string& ppid) { RecipeInfo info; auto rows = m_pDB->fetchResults("SELECT ppid, description, create_time FROM recipes WHERE ppid = '" + ppid + "';"); if (rows.empty()) { return info; } info.strPPID = rows[0][0]; info.strDescription = rows[0][1]; info.strCreateTime = rows[0][2]; auto devs = m_pDB->fetchResults("SELECT device_id, device_name, recipe_id FROM recipe_devices WHERE ppid = '" + ppid + "';"); for (const auto& dev : devs) { DeviceRecipe dr; dr.strPPID = ppid; try { dr.nDeviceID = std::stoi(dev[0]); dr.strDeviceName = dev[1]; dr.nRecipeID = std::stoi(dev[2]); } catch (...) { std::cerr << "Invalid data in recipe_devices for PPID: " << ppid << std::endl; continue; } info.vecDeviceList.push_back(dr); } return info; } int RecipeManager::getDeviceRecipeIDByID(const std::string& ppid, int nDeviceID) { if (!m_pDB || ppid.empty() || nDeviceID <= 0) { return -1; } std::ostringstream query; query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";"; auto result = m_pDB->fetchResults(query.str()); if (!result.empty() && !result[0].empty()) { try { return std::stoi(result[0][0]); } catch (...) { return -1; } } return -1; } int RecipeManager::getDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName) { if (!m_pDB || ppid.empty() || strDeviceName.empty()) { return -1; } std::ostringstream query; query << "SELECT recipe_id FROM recipe_devices WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';"; auto result = m_pDB->fetchResults(query.str()); if (!result.empty() && !result[0].empty()) { try { return std::stoi(result[0][0]); } catch (...) { return -1; } } return -1; } bool RecipeManager::deleteRecipeByPPID(const std::string& ppid) { if (!m_pDB) { return false; } std::lock_guard lock(m_mutex); return m_pDB->executeQuery("DELETE FROM recipes WHERE ppid = '" + ppid + "';"); } bool RecipeManager::updateRecipe(const RecipeInfo& recipe) { if (!m_pDB) { return false; } if (recipe.strPPID.empty()) { std::cerr << "Recipe PPID cannot be empty." << std::endl; return false; } std::lock_guard lock(m_mutex); deleteRecipeByPPID(recipe.strPPID); return addRecipe(recipe); } bool RecipeManager::updatePPID(const std::string& oldPPID, const std::string& newPPID) { if (!m_pDB || oldPPID.empty() || newPPID.empty()) { std::cerr << "[updatePPID] Invalid input." << std::endl; return false; } std::lock_guard lock(m_mutex); // ¼ì²éÊÇ·ñÒѾ­´æÔÚÏàͬµÄ newPPID auto check = m_pDB->fetchResults("SELECT COUNT(*) FROM recipes WHERE ppid = '" + newPPID + "';"); if (!check.empty() && !check[0].empty() && check[0][0] != "0") { std::cerr << "[updatePPID] New PPID already exists: " << newPPID << std::endl; return false; } m_pDB->executeQuery("BEGIN TRANSACTION;"); std::ostringstream sql; sql << "UPDATE recipes SET ppid = '" << newPPID << "' WHERE ppid = '" << oldPPID << "';"; if (!m_pDB->executeQuery(sql.str())) { std::cerr << "[updatePPID] Failed to update recipes table." << std::endl; m_pDB->executeQuery("ROLLBACK;"); return false; } m_pDB->executeQuery("COMMIT;"); return true; } bool RecipeManager::updateDescription(const std::string& ppid, const std::string& newDescription) { if (!m_pDB || ppid.empty()) { std::cerr << "[updateRecipeDescription] Invalid input." << std::endl; return false; } std::ostringstream oss; oss << "UPDATE recipes SET description = '" << newDescription << "' WHERE ppid = '" << ppid << "';"; std::lock_guard lock(m_mutex); return m_pDB->executeQuery(oss.str()); } bool RecipeManager::updateDeviceRecipeIDByID(const std::string& ppid, int nDeviceID, int nNewRecipeID) { if (!m_pDB || ppid.empty() || nDeviceID <= 0 || nNewRecipeID <= 0) { return false; } std::ostringstream query; std::lock_guard lock(m_mutex); query << "UPDATE recipe_devices SET recipe_id = " << nNewRecipeID << " WHERE ppid = '" << ppid << "' AND device_id = " << nDeviceID << ";"; return m_pDB->executeQuery(query.str()); } bool RecipeManager::updateDeviceRecipeIDByName(const std::string& ppid, const std::string& strDeviceName, int nNewRecipeID) { if (!m_pDB || ppid.empty() || strDeviceName.empty() || nNewRecipeID <= 0) { return false; } std::ostringstream query; std::lock_guard lock(m_mutex); query << "UPDATE recipe_devices SET recipe_id = " << nNewRecipeID << " WHERE ppid = '" << ppid << "' AND device_name = '" << strDeviceName << "';"; return m_pDB->executeQuery(query.str()); } void RecipeManager::insertMockData() { if (!m_pDB) { return; } RecipeInfo recipe; recipe.strPPID = "P1001"; recipe.strDescription = "Main Board Burn-in"; recipe.vecDeviceList = { {1, 101, "P1001","Burner A"}, {2, 102, "P1001", "Burner B"} }; addRecipe(recipe); } bool RecipeManager::readRecipeFile(const std::string& filename) { if (!m_pDB) { return false; } std::ifstream file(filename); if (!file.is_open()) { return false; } std::unordered_map recipeMap; std::string line; std::getline(file, line); // skip header while (std::getline(file, line)) { std::stringstream ss(line); std::string cell; std::string ppid, description, createTime; DeviceRecipe dev; std::getline(ss, ppid, ','); std::getline(ss, cell, ','); try { dev.nDeviceID = std::stoi(cell); } catch (...) { continue; } std::getline(ss, dev.strDeviceName, ','); std::getline(ss, cell, ','); try { dev.nRecipeID = std::stoi(cell); } catch (...) { continue; } std::getline(ss, description, ','); std::getline(ss, createTime, ','); dev.strPPID = ppid; auto& recipe = recipeMap[ppid]; recipe.strPPID = ppid; recipe.strDescription = description; recipe.strCreateTime = createTime; recipe.vecDeviceList.push_back(dev); } for (const auto& pair : recipeMap) { if (!updateRecipe(pair.second)) { std::cerr << "Failed to update recipe from file: " << pair.first << std::endl; } } return true; } bool RecipeManager::saveRecipeFile(const std::string& filename) { if (!m_pDB) { return false; } std::ofstream file(filename); if (!file.is_open()) { return false; } file << "PPID,DeviceID,DeviceName,RecipeID,Description,CreateTime\n"; auto recipes = getAllRecipes(); for (const auto& recipe : recipes) { for (const auto& dev : recipe.vecDeviceList) { file << recipe.strPPID << "," << dev.nDeviceID << "," << dev.strDeviceName << "," << dev.nRecipeID << "," << recipe.strDescription << "," << recipe.strCreateTime << "\n"; } } return true; }