Implemented datastore shared memory codebase
Changes: - Added SharedMemory module for file-backed memory-mapped storage - Added MappingInfo, FileHeader, RecordState and TrackedRecord infrastructure - Replaced CSV-based serialization with binary struct serialization - Added DataStore initialization and shutdown lifecycle management - Added datastore mutex synchronization for multi-process access - Added shared-memory mapping configuration for all datastore entities - Added generic loadRecords and saveRecords template infrastructure - Added automatic datastore directory creation and .dat file storage - Updated configuration to use binary datastore files and initial capacities - Added enum underlying types for serialization compatibility - Added password masking support for login, registration and password change flows - Added Visual Studio project configuration for shared-memory components - Added datastore-owned record caches for runtime object management - Updated datastore APIs to return cached tracked-record collections by reference - Added generic cache refresh and cleanup infrastructure - Updated save operations to persist datastore caches directly - Added automatic cache cleanup during datastore destruction - Prepared datastore for multi-process shared-memory persistence
This commit is contained in:
@@ -6,42 +6,232 @@ Date: 19-May-2026
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include "Map.h"
|
||||
|
||||
#include "MappingInfo.h"
|
||||
#include "TrackedRecord.h"
|
||||
#include "SharedMemory.h"
|
||||
class User;
|
||||
class Notification;
|
||||
class Service;
|
||||
class ComboPackage;
|
||||
class InventoryItem;
|
||||
class ServiceBooking;
|
||||
class JobCard;
|
||||
class InventoryItem;
|
||||
class Invoice;
|
||||
class Payment;
|
||||
|
||||
class DataStore
|
||||
{
|
||||
private:
|
||||
util::Map<std::string, User*> m_users;
|
||||
util::Map<std::string, Service*> m_services;
|
||||
util::Map<std::string, ComboPackage*> m_comboPackages;
|
||||
util::Map<std::string, ServiceBooking*> m_serviceBookings;
|
||||
util::Map<std::string, JobCard*> m_jobCards;
|
||||
util::Map<std::string, InventoryItem*> m_inventoryItems;
|
||||
util::Map<std::string, Invoice*> m_invoices;
|
||||
util::Map<std::string, Payment*> m_payments;
|
||||
DataStore() {}
|
||||
public:
|
||||
static DataStore& getInstance();
|
||||
DataStore();
|
||||
~DataStore();
|
||||
DataStore(const DataStore&) = delete;
|
||||
DataStore& operator=(const DataStore&) = delete;
|
||||
DataStore(DataStore&&) = delete;
|
||||
DataStore& operator=(DataStore&&) = delete;
|
||||
util::Map<std::string, User*>& getUsers();
|
||||
util::Map<std::string, Service*>& getServices();
|
||||
util::Map<std::string, ComboPackage*>& getComboPackages();
|
||||
util::Map<std::string, ServiceBooking*>& getServiceBookings();
|
||||
util::Map<std::string, JobCard*>& getJobCards();
|
||||
util::Map<std::string, InventoryItem*>& getInventoryItems();
|
||||
util::Map<std::string, Invoice*>& getInvoices();
|
||||
util::Map<std::string, Payment*>& getPayments();
|
||||
};
|
||||
HANDLE m_globalMutex;
|
||||
MappingInfo m_users;
|
||||
MappingInfo m_notifications;
|
||||
MappingInfo m_services;
|
||||
MappingInfo m_comboPackages;
|
||||
MappingInfo m_inventoryItems;
|
||||
MappingInfo m_serviceBookings;
|
||||
MappingInfo m_jobCards;
|
||||
MappingInfo m_invoices;
|
||||
MappingInfo m_payments;
|
||||
MappingInfo m_serviceManagementObservers;
|
||||
MappingInfo m_paymentManagementObservers;
|
||||
MappingInfo m_inventoryManagementObservers;
|
||||
util::Map<std::string, TrackedRecord<User>> m_userCache;
|
||||
util::Map<std::string, TrackedRecord<Notification>> m_notificationCache;
|
||||
util::Map<std::string, TrackedRecord<Service>> m_serviceCache;
|
||||
util::Map<std::string, TrackedRecord<ComboPackage>> m_comboPackageCache;
|
||||
util::Map<std::string, TrackedRecord<InventoryItem>> m_inventoryItemCache;
|
||||
util::Map<std::string, TrackedRecord<ServiceBooking>> m_serviceBookingCache;
|
||||
util::Map<std::string, TrackedRecord<JobCard>> m_jobCardCache;
|
||||
util::Map<std::string, TrackedRecord<Invoice>> m_invoiceCache;
|
||||
public:
|
||||
static DataStore& getInstance();
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
util::Map<std::string, TrackedRecord<User>>& getUsers();
|
||||
util::Map<std::string, TrackedRecord<Notification>>& getNotifications();
|
||||
util::Map<std::string, TrackedRecord<Service>>& getServices();
|
||||
util::Map<std::string, TrackedRecord<ComboPackage>>& getComboPackages();
|
||||
util::Map<std::string, TrackedRecord<InventoryItem>>& getInventoryItems();
|
||||
util::Map<std::string, TrackedRecord<ServiceBooking>>& getServiceBookings();
|
||||
util::Map<std::string, TrackedRecord<JobCard>>& getJobCards();
|
||||
util::Map<std::string, TrackedRecord<Invoice>>& getInvoices();
|
||||
util::Map<std::string, User*> getServiceManagementObservers();
|
||||
util::Map<std::string, User*> getPaymentManagementObservers();
|
||||
util::Map<std::string, User*> getInventoryManagementObservers();
|
||||
void saveUsers();
|
||||
void saveNotifications();
|
||||
void saveServices();
|
||||
void saveComboPackages();
|
||||
void saveInventoryItems();
|
||||
void saveServiceBookings();
|
||||
void saveJobCards();
|
||||
void saveInvoices();
|
||||
void saveServiceManagementObservers(util::Map<std::string, User*>& observers);
|
||||
void savePaymentManagementObservers(util::Map<std::string, User*>& observers);
|
||||
void saveInventoryManagementObservers(util::Map<std::string, User*>& observers);
|
||||
bool lockDataStore();
|
||||
bool unlockDataStore();
|
||||
private:
|
||||
template<typename TObject, typename TSerialized>
|
||||
util::Map<std::string, TrackedRecord<TObject>> loadRecords(MappingInfo& mapping);
|
||||
template<typename TObject, typename TSerialized>
|
||||
void saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records);
|
||||
template<typename TObject> void clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache);
|
||||
template<typename TObject> void refreshCache(util::Map<std::string, TrackedRecord<TObject>>&cache, util::Map<std::string, TrackedRecord<TObject>>&refreshedCache);
|
||||
};
|
||||
|
||||
/*
|
||||
Function: loadRecords
|
||||
Description: Loads all serialized records from the specified
|
||||
shared-memory mapping, deserializes them into
|
||||
application objects, and returns them as a map
|
||||
of tracked records. Each loaded record is marked
|
||||
as CLEAN and assigned its corresponding slot index
|
||||
within the mapping so that future modifications
|
||||
can be written back efficiently.
|
||||
Parameter:
|
||||
- mapping: Reference to the mapping containing the
|
||||
serialized records.
|
||||
Return type:
|
||||
util::Map<std::string, TrackedRecord<TObject>>
|
||||
A map containing all loaded records keyed by
|
||||
their unique identifier.
|
||||
*/
|
||||
template<typename TObject, typename TSerialized>
|
||||
util::Map<std::string, TrackedRecord<TObject>> DataStore::loadRecords(MappingInfo& mapping)
|
||||
{
|
||||
util::Map<std::string, TrackedRecord<TObject>> records;
|
||||
SharedMemory::ensureLatestMapping(mapping);
|
||||
size_t recordCount = SharedMemory::getRecordCount(mapping);
|
||||
for (size_t index = 0; index < recordCount; ++index)
|
||||
{
|
||||
TSerialized* serialized = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,index));
|
||||
TObject* object = TObject::deserialize(*serialized);
|
||||
TrackedRecord<TObject> trackedRecord;
|
||||
trackedRecord.data = object;
|
||||
trackedRecord.state = RecordState::CLEAN;
|
||||
trackedRecord.slotIndex = index;
|
||||
records.insert(object->getId(), trackedRecord);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: saveRecords
|
||||
Description: Persists all modified and newly added records
|
||||
contained in the provided map to the specified
|
||||
shared-memory mapping. Modified records overwrite
|
||||
their existing slots, while new records are
|
||||
appended to the end of the mapping. Records marked
|
||||
as CLEAN are ignored.
|
||||
Parameter:
|
||||
- mapping: Reference to the mapping where records are
|
||||
stored.
|
||||
- records: Map containing the records to be persisted.
|
||||
Return type: void
|
||||
*/
|
||||
template<typename TObject, typename TSerialized> void DataStore::saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records)
|
||||
{
|
||||
SharedMemory::ensureLatestMapping(mapping);
|
||||
for (int index = 0; index < records.getSize(); ++index)
|
||||
{
|
||||
TrackedRecord<TObject>& record = records.getValueAt(index);
|
||||
if (record.state == RecordState::CLEAN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TSerialized serialized = record.data->serialize();
|
||||
if (record.state == RecordState::MODIFIED)
|
||||
{
|
||||
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping, record.slotIndex));
|
||||
if (destination)
|
||||
{
|
||||
*destination = serialized;
|
||||
}
|
||||
}
|
||||
else if (record.state == RecordState::NEW_RECORD)
|
||||
{
|
||||
if (!SharedMemory::ensureCapacityForInsert(mapping))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
size_t recordCount = SharedMemory::getRecordCount(mapping);
|
||||
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,recordCount));
|
||||
*destination = serialized;
|
||||
SharedMemory::setRecordCount(mapping, recordCount + 1);
|
||||
record.slotIndex = recordCount;
|
||||
}
|
||||
record.state = RecordState::CLEAN;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Function: clearCache
|
||||
Description: Releases all objects owned by the cache and
|
||||
clears the cache contents.
|
||||
Parameters:
|
||||
- cache: Cache to be cleared.
|
||||
Returns:
|
||||
- None
|
||||
*/
|
||||
template<typename TObject>
|
||||
void DataStore::clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache)
|
||||
{
|
||||
for (int index = 0; index < cache.getSize(); ++index)
|
||||
{
|
||||
delete cache.getValueAt(index).data;
|
||||
cache.getValueAt(index).data = nullptr;
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
Function: refreshCache
|
||||
Description: Refreshes the cache while preserving object addresses
|
||||
for records that already exist. Existing objects are
|
||||
updated in-place so that pointers held elsewhere remain
|
||||
valid after the refresh.
|
||||
Parameters:
|
||||
- cache: Existing cache to refresh.
|
||||
- refreshedCache: Newly loaded cache contents.
|
||||
Returns:
|
||||
- None
|
||||
*/
|
||||
template<typename TObject>
|
||||
void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cache, util::Map<std::string, TrackedRecord<TObject>>& refreshedCache)
|
||||
{
|
||||
util::Map<std::string, TrackedRecord<TObject>> oldCache = cache;
|
||||
cache.clear();
|
||||
for (int index = 0; index < refreshedCache.getSize(); ++index)
|
||||
{
|
||||
const std::string& id = refreshedCache.getKeyAt(index);
|
||||
TrackedRecord<TObject>& refreshedRecord = refreshedCache.getValueAt(index);
|
||||
int oldIndex = oldCache.find(id);
|
||||
if (oldIndex != -1)
|
||||
{
|
||||
TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex);
|
||||
*oldRecord.data = *refreshedRecord.data;
|
||||
oldRecord.slotIndex = refreshedRecord.slotIndex;
|
||||
oldRecord.state = refreshedRecord.state;
|
||||
delete refreshedRecord.data;
|
||||
refreshedRecord.data = oldRecord.data;
|
||||
}
|
||||
cache.insert(id, refreshedRecord);
|
||||
}
|
||||
for (int index = 0; index < oldCache.getSize(); ++index)
|
||||
{
|
||||
const std::string& id = oldCache.getKeyAt(index);
|
||||
if (cache.find(id) == -1)
|
||||
{
|
||||
delete oldCache.getValueAt(index).data;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user