#include "server/database.h" #include "common/protocol.h" #include #include #include #include #include // ───────────────────────────────────────────────────────────────────────────── // Escape / unescape: protect '|', '\n', '\' in pipe-delimited fields // ───────────────────────────────────────────────────────────────────────────── std::string Database::escape(const std::string& s) { std::string out; out.reserve(s.size() + 4); for (char c : s) { if (c == '\\') out += "\\\\"; else if (c == '|') out += "\\|"; else if (c == '\n') out += "\\n"; else out += c; } return out; } std::string Database::unescape(const std::string& s) { std::string out; out.reserve(s.size()); for (size_t i = 0; i < s.size(); ++i) { if (s[i] == '\\' && i + 1 < s.size()) { ++i; if (s[i] == '\\') out += '\\'; else if (s[i] == '|') out += '|'; else if (s[i] == 'n') out += '\n'; else { out += '\\'; out += s[i]; } } else { out += s[i]; } } return out; } // ───────────────────────────────────────────────────────────────────────────── // split_pipe: split on '|' respecting backslash escapes // ───────────────────────────────────────────────────────────────────────────── static std::vector split_pipe(const std::string& line) { std::vector fields; std::string cur; for (size_t i = 0; i < line.size(); ++i) { if (line[i] == '\\' && i + 1 < line.size()) { cur += line[i]; cur += line[++i]; } else if (line[i] == '|') { fields.push_back(cur); cur.clear(); } else { cur += line[i]; } } fields.push_back(cur); return fields; } // ───────────────────────────────────────────────────────────────────────────── // List helpers (LS separator) // ───────────────────────────────────────────────────────────────────────────── static std::string ls_join_u64(const std::vector& v) { return join_u64(v, LS); } static std::string ls_join_u32(const std::vector& v) { return join_u32(v, LS); } static std::vector ls_split(const std::string& s) { if (s.empty()) return {}; return split(s, LS); } static std::vector ls_split_u64(const std::string& s) { std::vector out; for (const auto& p : split(s, LS)) if (!p.empty()) out.push_back(std::stoull(p)); return out; } static std::vector ls_split_u32(const std::string& s) { std::vector out; for (const auto& p : split(s, LS)) if (!p.empty()) out.push_back(static_cast(std::stoul(p))); return out; } // ───────────────────────────────────────────────────────────────────────────── // PartBase helpers // ───────────────────────────────────────────────────────────────────────────── static std::string base_serial(const PartBase& p) { return p.serial_number.has_value() ? p.serial_number.value() : "NULL"; } static void apply_base(PartBase& p, const std::map& kv) { if (auto it = kv.find(K_SERIAL); it != kv.end()) { if (it->second == "NULL") p.serial_number = std::nullopt; else p.serial_number = it->second; } } // ───────────────────────────────────────────────────────────────────────────── // Per-type apply helpers (static free functions) // ───────────────────────────────────────────────────────────────────────────── static void apply_memory_stick(MemoryStick& m, const std::map& kv) { apply_base(m, kv); if (auto it = kv.find(K_SPEED_MHZ); it != kv.end()) m.speed_mhz = static_cast(std::stoul(it->second)); if (auto it = kv.find(K_SIZE_MB); it != kv.end()) m.size_mb = std::stoull(it->second); if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) m.manufacturer = it->second; if (auto it = kv.find(K_CHANNEL_CONFIG); it != kv.end()) m.channel_config = it->second; if (auto it = kv.find(K_OTHER_INFO); it != kv.end()) m.other_info = it->second; if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second; } static void apply_memory_slot(MemorySlot& m, const std::map& kv) { apply_base(m, kv); if (auto it = kv.find(K_ALLOWED_SPEED); it != kv.end()) m.allowed_speed_mhz = static_cast(std::stoul(it->second)); if (auto it = kv.find(K_ALLOWED_SIZE); it != kv.end()) m.allowed_size_mb = std::stoull(it->second); if (auto it = kv.find(K_INSTALLED_STICK); it != kv.end()) { if (it->second == "NULL") m.installed_stick_id = std::nullopt; else m.installed_stick_id = static_cast(std::stoul(it->second)); } if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second; } static void apply_cpu(CPU& c, const std::map& kv) { apply_base(c, kv); if (auto it = kv.find(K_NAME); it != kv.end()) c.name = it->second; if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) c.manufacturer = it->second; if (auto it = kv.find(K_SPEED_GHZ); it != kv.end()) c.speed_ghz = std::stod(it->second); if (auto it = kv.find(K_CORES); it != kv.end()) c.cores = static_cast(std::stoul(it->second)); if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast(std::stoul(it->second)); if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second; } static void apply_cpu_slot(CPUSlot& c, const std::map& kv) { apply_base(c, kv); if (auto it = kv.find(K_FORM_FACTOR); it != kv.end()) c.form_factor = it->second; if (auto it = kv.find(K_INSTALLED_CPU); it != kv.end()) { if (it->second == "NULL") c.installed_cpu_id = std::nullopt; else c.installed_cpu_id = static_cast(std::stoul(it->second)); } if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second; } static void apply_disk(Disk& d, const std::map& kv) { apply_base(d, kv); if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) d.manufacturer = it->second; if (auto it = kv.find(K_MODEL); it != kv.end()) d.model = it->second; if (auto it = kv.find(K_GENERATION); it != kv.end()) d.generation = it->second; if (auto it = kv.find(K_CONN_TYPE); it != kv.end()) d.connection_type = it->second; if (auto it = kv.find(K_CONN_SPEED); it != kv.end()) d.connection_speed_mbps = std::stoull(it->second); if (auto it = kv.find(K_DISK_SPEED); it != kv.end()) d.disk_speed_mbps = std::stoull(it->second); if (auto it = kv.find(K_DISK_SIZE); it != kv.end()) d.disk_size_gb = std::stoull(it->second); if (auto it = kv.find(K_DISK_TYPE); it != kv.end()) d.disk_type = it->second; if (auto it = kv.find(K_AGE_YEARS); it != kv.end()) d.age_years = std::stoi(it->second); if (auto it = kv.find(K_PARTITION_IDS); it != kv.end()) d.partition_ids = ls_split(it->second); if (auto it = kv.find(K_PARTITION_SIZES); it != kv.end()) d.partition_sizes_gb = ls_split_u64(it->second); if (auto it = kv.find(K_VM_HOSTNAMES); it != kv.end()) d.vm_hostnames = ls_split(it->second); if (auto it = kv.find(K_VM_SERVER_IDS); it != kv.end()) d.vm_server_ids = ls_split_u32(it->second); if (auto it = kv.find(K_DEVICE_NAME); it != kv.end()) d.device_name = it->second; } static void apply_nic(NetworkCard& n, const std::map& kv) { apply_base(n, kv); if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) n.manufacturer = it->second; if (auto it = kv.find(K_MODEL); it != kv.end()) n.model = it->second; if (auto it = kv.find(K_AGE_YEARS); it != kv.end()) n.age_years = std::stoi(it->second); if (auto it = kv.find(K_CONN_TYPE); it != kv.end()) n.connection_type = it->second; if (auto it = kv.find(K_CONN_SPEED); it != kv.end()) n.connection_speed_mbps = std::stoull(it->second); if (auto it = kv.find(K_MAC); it != kv.end()) n.mac_address = it->second; if (auto it = kv.find(K_IPS); it != kv.end()) n.ip_addresses = ls_split(it->second); if (auto it = kv.find(K_DHCP); it != kv.end()) n.dhcp = (it->second == "1" || it->second == "true"); } // ───────────────────────────────────────────────────────────────────────────── // Wire-row serializers // type_namepart_idserver_idserial_or_NULLlast_updatedk1v1... // ───────────────────────────────────────────────────────────────────────────── static std::string base_prefix(const char* type_name, const PartBase& p) { return std::string(type_name) + FS + std::to_string(p.part_id) + FS + std::to_string(p.server_id) + FS + base_serial(p) + FS + std::to_string(p.last_updated); } std::string Database::serialize_memory_stick(const MemoryStick& m) const { return base_prefix(PTYPE_MEMORY_STICK, m) + FS + K_SPEED_MHZ + FS + std::to_string(m.speed_mhz) + FS + K_SIZE_MB + FS + std::to_string(m.size_mb) + FS + K_MANUFACTURER + FS + m.manufacturer + FS + K_CHANNEL_CONFIG + FS + m.channel_config + FS + K_OTHER_INFO + FS + m.other_info + FS + K_LOCATOR + FS + m.locator + "\n"; } std::string Database::serialize_memory_slot(const MemorySlot& m) const { std::string installed = m.installed_stick_id.has_value() ? std::to_string(m.installed_stick_id.value()) : "NULL"; return base_prefix(PTYPE_MEMORY_SLOT, m) + FS + K_ALLOWED_SPEED + FS + std::to_string(m.allowed_speed_mhz) + FS + K_ALLOWED_SIZE + FS + std::to_string(m.allowed_size_mb) + FS + K_INSTALLED_STICK + FS + installed + FS + K_LOCATOR + FS + m.locator + "\n"; } std::string Database::serialize_cpu(const CPU& c) const { return base_prefix(PTYPE_CPU, c) + FS + K_NAME + FS + c.name + FS + K_MANUFACTURER + FS + c.manufacturer + FS + K_SPEED_GHZ + FS + std::to_string(c.speed_ghz) + FS + K_CORES + FS + std::to_string(c.cores) + FS + K_THREADS + FS + std::to_string(c.threads) + FS + K_SOCKET + FS + c.socket_designation + "\n"; } std::string Database::serialize_cpu_slot(const CPUSlot& c) const { std::string installed = c.installed_cpu_id.has_value() ? std::to_string(c.installed_cpu_id.value()) : "NULL"; return base_prefix(PTYPE_CPU_SLOT, c) + FS + K_FORM_FACTOR + FS + c.form_factor + FS + K_INSTALLED_CPU + FS + installed + FS + K_SOCKET + FS + c.socket_designation + "\n"; } std::string Database::serialize_disk(const Disk& d) const { // Build per-list string: escape each element, join with LS auto esc_join = [](const std::vector& v) -> std::string { std::string out; for (size_t i = 0; i < v.size(); ++i) { if (i) out += LS; // escape element (pipe/backslash could appear in partition ids or hostnames) for (char c : v[i]) { if (c == '\\') out += "\\\\"; else if (c == '|') out += "\\|"; else out += c; } } return out; }; return base_prefix(PTYPE_DISK, d) + FS + K_MANUFACTURER + FS + d.manufacturer + FS + K_MODEL + FS + d.model + FS + K_GENERATION + FS + d.generation + FS + K_CONN_TYPE + FS + d.connection_type + FS + K_CONN_SPEED + FS + std::to_string(d.connection_speed_mbps) + FS + K_DISK_SPEED + FS + std::to_string(d.disk_speed_mbps) + FS + K_DISK_SIZE + FS + std::to_string(d.disk_size_gb) + FS + K_DISK_TYPE + FS + d.disk_type + FS + K_AGE_YEARS + FS + std::to_string(d.age_years) + FS + K_PARTITION_IDS + FS + esc_join(d.partition_ids) + FS + K_PARTITION_SIZES + FS + ls_join_u64(d.partition_sizes_gb) + FS + K_VM_HOSTNAMES + FS + esc_join(d.vm_hostnames) + FS + K_VM_SERVER_IDS + FS + ls_join_u32(d.vm_server_ids) + FS + K_DEVICE_NAME + FS + d.device_name + "\n"; } std::string Database::serialize_nic(const NetworkCard& n) const { auto esc_join = [](const std::vector& v) -> std::string { std::string out; for (size_t i = 0; i < v.size(); ++i) { if (i) out += LS; for (char c : v[i]) { if (c == '\\') out += "\\\\"; else if (c == '|') out += "\\|"; else out += c; } } return out; }; return base_prefix(PTYPE_NIC, n) + FS + K_MANUFACTURER + FS + n.manufacturer + FS + K_MODEL + FS + n.model + FS + K_AGE_YEARS + FS + std::to_string(n.age_years) + FS + K_CONN_TYPE + FS + n.connection_type + FS + K_CONN_SPEED + FS + std::to_string(n.connection_speed_mbps) + FS + K_MAC + FS + n.mac_address + FS + K_IPS + FS + esc_join(n.ip_addresses) + FS + K_DHCP + FS + (n.dhcp ? "1" : "0") + "\n"; } // ───────────────────────────────────────────────────────────────────────────── // Constructor // ───────────────────────────────────────────────────────────────────────────── Database::Database(std::string path) : path_(std::move(path)) {} // ───────────────────────────────────────────────────────────────────────────── // alloc_part_id — caller must hold mu_ // ───────────────────────────────────────────────────────────────────────────── uint32_t Database::alloc_part_id() { return inv_.next_part_id++; } // ───────────────────────────────────────────────────────────────────────────── // save_nolock / save // ───────────────────────────────────────────────────────────────────────────── bool Database::save_nolock() { std::ofstream f(path_, std::ios::trunc); if (!f.is_open()) { std::cerr << "[db] cannot open " << path_ << " for writing\n"; return false; } f << "# device-inventory database\n"; f << "META|" << inv_.next_server_id << "|" << inv_.next_part_type_id << "|" << inv_.next_part_id << "\n"; for (const auto& s : inv_.servers) { f << "S|" << s.id << "|" << escape(s.name) << "|" << escape(s.hostname) << "|" << escape(s.location) << "|" << escape(s.description) << "\n"; } for (const auto& pt : inv_.part_types) { f << "PT|" << pt.id << "|" << escape(pt.name) << "|" << escape(pt.description) << "\n"; } // Helper lambda: write LS-joined list of escaped strings as a single pipe field auto write_str_list = [](std::ofstream& out, const std::vector& v) { for (size_t i = 0; i < v.size(); ++i) { if (i) out << static_cast(LS); // escape individual elements (protect | and \ within each element) for (char c : v[i]) { if (c == '\\') out << "\\\\"; else if (c == '|') out << "\\|"; else out << c; } } }; for (const auto& m : inv_.memory_sticks) { f << "MS|" << m.part_id << "|" << m.server_id << "|" << escape(base_serial(m)) << "|" << m.last_updated << "|" << m.speed_mhz << "|" << m.size_mb << "|" << escape(m.manufacturer) << "|" << escape(m.channel_config) << "|" << escape(m.other_info) << "|" << escape(m.locator) << "\n"; } for (const auto& m : inv_.memory_slots) { std::string inst = m.installed_stick_id.has_value() ? std::to_string(m.installed_stick_id.value()) : "NULL"; f << "MSLOT|" << m.part_id << "|" << m.server_id << "|" << escape(base_serial(m)) << "|" << m.last_updated << "|" << m.allowed_speed_mhz << "|" << m.allowed_size_mb << "|" << inst << "|" << escape(m.locator) << "\n"; } for (const auto& c : inv_.cpus) { f << "CPU|" << c.part_id << "|" << c.server_id << "|" << escape(base_serial(c)) << "|" << c.last_updated << "|" << escape(c.name) << "|" << escape(c.manufacturer) << "|" << c.speed_ghz << "|" << c.cores << "|" << c.threads << "|" << escape(c.socket_designation) << "\n"; } for (const auto& c : inv_.cpu_slots) { std::string inst = c.installed_cpu_id.has_value() ? std::to_string(c.installed_cpu_id.value()) : "NULL"; f << "CPUSLOT|" << c.part_id << "|" << c.server_id << "|" << escape(base_serial(c)) << "|" << c.last_updated << "|" << escape(c.form_factor) << "|" << inst << "|" << escape(c.socket_designation) << "\n"; } for (const auto& d : inv_.disks) { f << "DISK|" << d.part_id << "|" << d.server_id << "|" << escape(base_serial(d)) << "|" << d.last_updated << "|" << escape(d.manufacturer) << "|" << escape(d.model) << "|" << escape(d.generation) << "|" << escape(d.connection_type) << "|" << d.connection_speed_mbps << "|" << d.disk_speed_mbps << "|" << d.disk_size_gb << "|" << escape(d.disk_type) << "|" << d.age_years << "|"; write_str_list(f, d.partition_ids); f << "|"; // partition_sizes_gb: numeric, no escape needed for (size_t i = 0; i < d.partition_sizes_gb.size(); ++i) { if (i) f << static_cast(LS); f << d.partition_sizes_gb[i]; } f << "|"; write_str_list(f, d.vm_hostnames); f << "|"; for (size_t i = 0; i < d.vm_server_ids.size(); ++i) { if (i) f << static_cast(LS); f << d.vm_server_ids[i]; } f << "|" << escape(d.device_name) << "\n"; } for (const auto& n : inv_.network_cards) { f << "NIC|" << n.part_id << "|" << n.server_id << "|" << escape(base_serial(n)) << "|" << n.last_updated << "|" << escape(n.manufacturer) << "|" << escape(n.model) << "|" << n.age_years << "|" << escape(n.connection_type) << "|" << n.connection_speed_mbps << "|" << escape(n.mac_address) << "|"; write_str_list(f, n.ip_addresses); f << "|" << (n.dhcp ? 1 : 0) << "\n"; } return true; } bool Database::save() { std::lock_guard lk(mu_); return save_nolock(); } // ───────────────────────────────────────────────────────────────────────────── // load // ───────────────────────────────────────────────────────────────────────────── bool Database::load() { std::lock_guard lk(mu_); std::ifstream f(path_); if (!f.is_open()) { inv_ = Inventory{}; return true; } Inventory fresh; std::string line; while (std::getline(f, line)) { if (line.empty() || line[0] == '#') continue; auto flds = split_pipe(line); if (flds.empty()) continue; const auto& rec = flds[0]; auto get = [&](size_t i) -> std::string { return i < flds.size() ? unescape(flds[i]) : ""; }; auto getu32 = [&](size_t i) -> uint32_t { return i < flds.size() ? static_cast(std::stoul(flds[i])) : 0; }; auto getu64 = [&](size_t i) -> uint64_t { return i < flds.size() ? std::stoull(flds[i]) : 0ULL; }; auto geti64 = [&](size_t i) -> int64_t { return i < flds.size() ? std::stoll(flds[i]) : 0LL; }; // serial field: raw flds[i] (not unescaped via get) but compare to "NULL" auto get_serial = [&](size_t i) -> std::optional { if (i >= flds.size() || flds[i] == "NULL") return std::nullopt; return unescape(flds[i]); }; auto get_opt_u32 = [&](size_t i) -> std::optional { if (i >= flds.size() || flds[i] == "NULL") return std::nullopt; return static_cast(std::stoul(flds[i])); }; if (rec == "META" && flds.size() >= 4) { fresh.next_server_id = getu32(1); fresh.next_part_type_id = getu32(2); fresh.next_part_id = getu32(3); } else if (rec == "S" && flds.size() >= 6) { Server s; s.id = getu32(1); s.name = get(2); s.hostname = get(3); s.location = get(4); s.description = get(5); fresh.servers.push_back(std::move(s)); } else if (rec == "PT" && flds.size() >= 4) { PartType pt; pt.id = getu32(1); pt.name = get(2); pt.description = get(3); fresh.part_types.push_back(std::move(pt)); } else if (rec == "MS" && flds.size() >= 10) { MemoryStick m; m.part_id = getu32(1); m.server_id = getu32(2); m.serial_number = get_serial(3); m.last_updated = geti64(4); m.speed_mhz = getu32(5); m.size_mb = getu64(6); m.manufacturer = get(7); m.channel_config = get(8); m.other_info = get(9); m.locator = get(10); fresh.memory_sticks.push_back(std::move(m)); } else if (rec == "MSLOT" && flds.size() >= 8) { MemorySlot m; m.part_id = getu32(1); m.server_id = getu32(2); m.serial_number = get_serial(3); m.last_updated = geti64(4); m.allowed_speed_mhz = getu32(5); m.allowed_size_mb = getu64(6); m.installed_stick_id = get_opt_u32(7); m.locator = get(8); fresh.memory_slots.push_back(std::move(m)); } else if (rec == "CPU" && flds.size() >= 10) { CPU c; c.part_id = getu32(1); c.server_id = getu32(2); c.serial_number = get_serial(3); c.last_updated = geti64(4); c.name = get(5); c.manufacturer = get(6); c.speed_ghz = flds.size() > 7 ? std::stod(flds[7]) : 0.0; c.cores = getu32(8); c.threads = getu32(9); c.socket_designation = get(10); fresh.cpus.push_back(std::move(c)); } else if (rec == "CPUSLOT" && flds.size() >= 7) { CPUSlot c; c.part_id = getu32(1); c.server_id = getu32(2); c.serial_number = get_serial(3); c.last_updated = geti64(4); c.form_factor = get(5); c.installed_cpu_id = get_opt_u32(6); c.socket_designation = get(7); fresh.cpu_slots.push_back(std::move(c)); } else if (rec == "DISK" && flds.size() >= 18) { Disk d; d.part_id = getu32(1); d.server_id = getu32(2); d.serial_number = get_serial(3); d.last_updated = geti64(4); d.manufacturer = get(5); d.model = get(6); d.generation = get(7); d.connection_type = get(8); d.connection_speed_mbps = getu64(9); d.disk_speed_mbps = getu64(10); d.disk_size_gb = getu64(11); d.disk_type = get(12); d.age_years = flds.size() > 13 ? std::stoi(flds[13]) : -1; // Note: flds[14..17] have NOT been through unescape() yet (we used raw flds) // Re-split using the raw (escaped) field and unescape each element auto uesc_split_raw = [](const std::string& raw) -> std::vector { if (raw.empty()) return {}; std::vector out; for (const auto& elem : split(raw, LS)) { std::string u; for (size_t i = 0; i < elem.size(); ++i) { if (elem[i] == '\\' && i+1 < elem.size()) { ++i; if (elem[i] == '\\') u += '\\'; else if (elem[i] == '|') u += '|'; else if (elem[i] == 'n') u += '\n'; else { u += '\\'; u += elem[i]; } } else u += elem[i]; } out.push_back(u); } return out; }; d.partition_ids = uesc_split_raw(flds.size() > 14 ? flds[14] : ""); d.partition_sizes_gb = ls_split_u64(flds.size() > 15 ? flds[15] : ""); d.vm_hostnames = uesc_split_raw(flds.size() > 16 ? flds[16] : ""); d.vm_server_ids = ls_split_u32(flds.size() > 17 ? flds[17] : ""); d.device_name = get(18); fresh.disks.push_back(std::move(d)); } else if (rec == "NIC" && flds.size() >= 13) { NetworkCard n; n.part_id = getu32(1); n.server_id = getu32(2); n.serial_number = get_serial(3); n.last_updated = geti64(4); n.manufacturer = get(5); n.model = get(6); n.age_years = flds.size() > 7 ? std::stoi(flds[7]) : -1; n.connection_type = get(8); n.connection_speed_mbps= getu64(9); n.mac_address = get(10); // ip_addresses: LS separated in flds[11] { auto& raw = flds[11]; for (auto& p : split(raw, LS)) n.ip_addresses.push_back(unescape(p)); } n.dhcp = (flds.size() > 12 && flds[12] == "1"); fresh.network_cards.push_back(std::move(n)); } } inv_ = std::move(fresh); return true; } // ───────────────────────────────────────────────────────────────────────────── // Servers // ───────────────────────────────────────────────────────────────────────────── Server Database::add_server(std::string name, std::string hostname, std::string location, std::string description) { std::lock_guard lk(mu_); Server s; s.id = inv_.next_server_id++; s.name = std::move(name); s.hostname = std::move(hostname); s.location = std::move(location); s.description = std::move(description); inv_.servers.push_back(s); save_nolock(); return s; } std::vector Database::list_servers() const { std::lock_guard lk(mu_); return inv_.servers; } std::optional Database::get_server(uint32_t id) const { std::lock_guard lk(mu_); for (const auto& s : inv_.servers) if (s.id == id) return s; return std::nullopt; } bool Database::edit_server(uint32_t id, const std::string& field, const std::string& value) { std::lock_guard lk(mu_); for (auto& s : inv_.servers) { if (s.id != id) continue; if (field == "name") s.name = value; else if (field == "hostname") s.hostname = value; else if (field == "location") s.location = value; else if (field == "description") s.description = value; else return false; save_nolock(); return true; } return false; } bool Database::remove_server(uint32_t id) { std::lock_guard lk(mu_); auto& v = inv_.servers; auto it = std::find_if(v.begin(), v.end(), [id](const Server& s){ return s.id == id; }); if (it == v.end()) return false; v.erase(it); save_nolock(); return true; } // ───────────────────────────────────────────────────────────────────────────── // Part types // ───────────────────────────────────────────────────────────────────────────── PartType Database::add_part_type(std::string name, std::string description) { std::lock_guard lk(mu_); PartType pt; pt.id = inv_.next_part_type_id++; pt.name = std::move(name); pt.description = std::move(description); inv_.part_types.push_back(pt); save_nolock(); return pt; } std::vector Database::list_part_types() const { std::lock_guard lk(mu_); return inv_.part_types; } std::optional Database::get_part_type(uint32_t id) const { std::lock_guard lk(mu_); for (const auto& pt : inv_.part_types) if (pt.id == id) return pt; return std::nullopt; } bool Database::edit_part_type(uint32_t id, const std::string& field, const std::string& value) { std::lock_guard lk(mu_); for (auto& pt : inv_.part_types) { if (pt.id != id) continue; if (field == "name") pt.name = value; else if (field == "description") pt.description = value; else return false; save_nolock(); return true; } return false; } bool Database::remove_part_type(uint32_t id) { std::lock_guard lk(mu_); auto& v = inv_.part_types; auto it = std::find_if(v.begin(), v.end(), [id](const PartType& pt){ return pt.id == id; }); if (it == v.end()) return false; v.erase(it); save_nolock(); return true; } // ───────────────────────────────────────────────────────────────────────────── // add_part_nolock — caller must hold mu_ // ───────────────────────────────────────────────────────────────────────────── uint32_t Database::add_part_nolock(const std::string& type_name, uint32_t server_id, const std::map& kv) { uint32_t pid = alloc_part_id(); int64_t now = static_cast(time(nullptr)); if (type_name == PTYPE_MEMORY_STICK) { MemoryStick m; m.part_id = pid; m.server_id = server_id; m.last_updated = now; apply_memory_stick(m, kv); inv_.memory_sticks.push_back(std::move(m)); } else if (type_name == PTYPE_MEMORY_SLOT) { MemorySlot m; m.part_id = pid; m.server_id = server_id; m.last_updated = now; apply_memory_slot(m, kv); inv_.memory_slots.push_back(std::move(m)); } else if (type_name == PTYPE_CPU) { CPU c; c.part_id = pid; c.server_id = server_id; c.last_updated = now; apply_cpu(c, kv); inv_.cpus.push_back(std::move(c)); } else if (type_name == PTYPE_CPU_SLOT) { CPUSlot c; c.part_id = pid; c.server_id = server_id; c.last_updated = now; apply_cpu_slot(c, kv); inv_.cpu_slots.push_back(std::move(c)); } else if (type_name == PTYPE_DISK) { Disk d; d.part_id = pid; d.server_id = server_id; d.last_updated = now; apply_disk(d, kv); inv_.disks.push_back(std::move(d)); } else if (type_name == PTYPE_NIC) { NetworkCard n; n.part_id = pid; n.server_id = server_id; n.last_updated = now; apply_nic(n, kv); inv_.network_cards.push_back(std::move(n)); } else { // Unknown type — no-op, reclaim the id by decrementing --inv_.next_part_id; return 0; } save_nolock(); return pid; } // ───────────────────────────────────────────────────────────────────────────── // add_part (public, locks) // ───────────────────────────────────────────────────────────────────────────── uint32_t Database::add_part(const std::string& type_name, uint32_t server_id, const std::map& kv) { std::lock_guard lk(mu_); return add_part_nolock(type_name, server_id, kv); } // ───────────────────────────────────────────────────────────────────────────── // upsert_part // ───────────────────────────────────────────────────────────────────────────── uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id, const std::map& kv) { std::lock_guard lk(mu_); int64_t now = static_cast(time(nullptr)); if (type_name == PTYPE_NIC) { auto mac_it = kv.find(K_MAC); if (mac_it != kv.end() && !mac_it->second.empty()) { for (auto& n : inv_.network_cards) { if (n.server_id == server_id && n.mac_address == mac_it->second) { apply_nic(n, kv); n.last_updated = now; save_nolock(); return n.part_id; } } } } else { auto ser_it = kv.find(K_SERIAL); if (ser_it != kv.end() && ser_it->second != "NULL" && !ser_it->second.empty()) { const std::string& serial = ser_it->second; // Search in the collection matching type_name auto try_update = [&](auto& vec) -> uint32_t { for (auto& p : vec) { if (p.server_id == server_id && p.serial_number.has_value() && p.serial_number.value() == serial) { if constexpr (std::is_same_v, MemoryStick>) apply_memory_stick(p, kv); else if constexpr (std::is_same_v, MemorySlot>) apply_memory_slot(p, kv); else if constexpr (std::is_same_v, CPU>) apply_cpu(p, kv); else if constexpr (std::is_same_v, CPUSlot>) apply_cpu_slot(p, kv); else if constexpr (std::is_same_v, Disk>) apply_disk(p, kv); p.last_updated = now; save_nolock(); return p.part_id; } } return 0u; }; uint32_t found = 0; if (type_name == PTYPE_MEMORY_STICK) found = try_update(inv_.memory_sticks); else if (type_name == PTYPE_MEMORY_SLOT) found = try_update(inv_.memory_slots); else if (type_name == PTYPE_CPU) found = try_update(inv_.cpus); else if (type_name == PTYPE_CPU_SLOT) found = try_update(inv_.cpu_slots); else if (type_name == PTYPE_DISK) found = try_update(inv_.disks); if (found) return found; } // Natural key fallback when no serial — prevents duplicate inserts on each run if (type_name == PTYPE_MEMORY_STICK || type_name == PTYPE_MEMORY_SLOT) { auto loc_it = kv.find(K_LOCATOR); if (loc_it != kv.end() && !loc_it->second.empty()) { const std::string& locator = loc_it->second; auto try_locator = [&](auto& vec, auto apply_fn) -> uint32_t { for (auto& p : vec) { if (p.server_id == server_id && p.locator == locator) { apply_fn(p, kv); p.last_updated = now; save_nolock(); return p.part_id; } } return 0u; }; uint32_t found = 0; if (type_name == PTYPE_MEMORY_STICK) found = try_locator(inv_.memory_sticks, apply_memory_stick); else if (type_name == PTYPE_MEMORY_SLOT) found = try_locator(inv_.memory_slots, apply_memory_slot); if (found) return found; } } else if (type_name == PTYPE_CPU || type_name == PTYPE_CPU_SLOT) { auto sock_it = kv.find(K_SOCKET); if (sock_it != kv.end() && !sock_it->second.empty()) { const std::string& socket = sock_it->second; auto try_socket = [&](auto& vec, auto apply_fn) -> uint32_t { for (auto& p : vec) { if (p.server_id == server_id && p.socket_designation == socket) { apply_fn(p, kv); p.last_updated = now; save_nolock(); return p.part_id; } } return 0u; }; uint32_t found = 0; if (type_name == PTYPE_CPU) found = try_socket(inv_.cpus, apply_cpu); else if (type_name == PTYPE_CPU_SLOT) found = try_socket(inv_.cpu_slots, apply_cpu_slot); if (found) return found; } } else if (type_name == PTYPE_DISK) { auto dev_it = kv.find(K_DEVICE_NAME); if (dev_it != kv.end() && !dev_it->second.empty()) { const std::string& dev_name = dev_it->second; for (auto& d : inv_.disks) { if (d.server_id == server_id && d.device_name == dev_name) { apply_disk(d, kv); d.last_updated = now; save_nolock(); return d.part_id; } } } } } // Not found — insert return add_part_nolock(type_name, server_id, kv); } // ───────────────────────────────────────────────────────────────────────────── // edit_part // ───────────────────────────────────────────────────────────────────────────── bool Database::edit_part(uint32_t part_id, const std::map& kv) { std::lock_guard lk(mu_); int64_t now = static_cast(time(nullptr)); auto try_edit = [&](auto& vec, auto apply_fn) -> bool { for (auto& p : vec) { if (p.part_id != part_id) continue; apply_fn(p, kv); p.last_updated = now; save_nolock(); return true; } return false; }; if (try_edit(inv_.memory_sticks, apply_memory_stick)) return true; if (try_edit(inv_.memory_slots, apply_memory_slot)) return true; if (try_edit(inv_.cpus, apply_cpu)) return true; if (try_edit(inv_.cpu_slots, apply_cpu_slot)) return true; if (try_edit(inv_.disks, apply_disk)) return true; if (try_edit(inv_.network_cards, apply_nic)) return true; return false; } // ───────────────────────────────────────────────────────────────────────────── // remove_part // ───────────────────────────────────────────────────────────────────────────── bool Database::remove_part(uint32_t part_id) { std::lock_guard lk(mu_); auto try_remove = [&](auto& vec) -> bool { auto it = std::find_if(vec.begin(), vec.end(), [part_id](const auto& p){ return p.part_id == part_id; }); if (it == vec.end()) return false; vec.erase(it); return true; }; bool removed = try_remove(inv_.memory_sticks) || try_remove(inv_.memory_slots) || try_remove(inv_.cpus) || try_remove(inv_.cpu_slots) || try_remove(inv_.disks) || try_remove(inv_.network_cards); if (removed) save_nolock(); return removed; } // ───────────────────────────────────────────────────────────────────────────── // list_parts / get_part_row // ───────────────────────────────────────────────────────────────────────────── std::vector Database::list_parts(uint32_t server_id, const std::string& type_filter) const { std::lock_guard lk(mu_); std::vector rows; bool all = type_filter.empty(); if (all || type_filter == PTYPE_MEMORY_STICK) for (const auto& m : inv_.memory_sticks) if (m.server_id == server_id) rows.push_back(serialize_memory_stick(m)); if (all || type_filter == PTYPE_MEMORY_SLOT) for (const auto& m : inv_.memory_slots) if (m.server_id == server_id) rows.push_back(serialize_memory_slot(m)); if (all || type_filter == PTYPE_CPU) for (const auto& c : inv_.cpus) if (c.server_id == server_id) rows.push_back(serialize_cpu(c)); if (all || type_filter == PTYPE_CPU_SLOT) for (const auto& c : inv_.cpu_slots) if (c.server_id == server_id) rows.push_back(serialize_cpu_slot(c)); if (all || type_filter == PTYPE_DISK) for (const auto& d : inv_.disks) if (d.server_id == server_id) rows.push_back(serialize_disk(d)); if (all || type_filter == PTYPE_NIC) for (const auto& n : inv_.network_cards) if (n.server_id == server_id) rows.push_back(serialize_nic(n)); return rows; } std::string Database::get_part_row(uint32_t part_id) const { std::lock_guard lk(mu_); for (const auto& m : inv_.memory_sticks) if (m.part_id == part_id) return serialize_memory_stick(m); for (const auto& m : inv_.memory_slots) if (m.part_id == part_id) return serialize_memory_slot(m); for (const auto& c : inv_.cpus) if (c.part_id == part_id) return serialize_cpu(c); for (const auto& c : inv_.cpu_slots) if (c.part_id == part_id) return serialize_cpu_slot(c); for (const auto& d : inv_.disks) if (d.part_id == part_id) return serialize_disk(d); for (const auto& n : inv_.network_cards) if (n.part_id == part_id) return serialize_nic(n); return ""; } // ───────────────────────────────────────────────────────────────────────────── // get_nics_for_server // ───────────────────────────────────────────────────────────────────────────── std::vector Database::get_nics_for_server(uint32_t server_id) const { std::lock_guard lk(mu_); std::vector result; for (const auto& n : inv_.network_cards) if (n.server_id == server_id) result.push_back(n); return result; }