You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/Module/GeoSigmaDraw/StorageFactory.cpp

621 lines
16 KiB
C++

#include "stdafx.h"
#include "StorageFactory.h"
#include <chrono>
#include <filesystem>
#include <optional>
#include <mutex>
#include "sqlite3.h"
#include "Util.h"
struct BaseRecord
{
int64_t id;
string filePath;
string dbPath;
std::string createAt;
};
class SQLiteStorage : public IActionStorage
{
public:
SQLiteStorage(const std::filesystem::path& backupDir, const std::string& dbName)
: m_backupDir(backupDir),
m_dbName(dbName),
m_baseStorage(MakeBaseStorage())
{
std::vector<BaseRecord> records = GetAllBaseRecords();
for (const auto& record : records)
{
m_cacheMap[record.filePath] = record.dbPath;
}
}
~SQLiteStorage()
{
for (auto& pair : m_cacheStorage)
{
sqlite3_free(pair.second);
}
}
bool InsertFileData(const std::string& filePath, FileData& fileData) override
{
if (!ExistFilePath(filePath))
{
CreateFileMapInfo(filePath);
}
sqlite3* storage = GetStorage(m_cacheMap[filePath]);
return InsertFileData(storage, fileData);
}
std::optional<FileData> RetrieveFileData(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return std::nullopt;
}
sqlite3* storage = GetStorage(it->second);
auto fileDatas = GetAllFileData(storage);
if (fileDatas.empty())
{
return std::nullopt;
}
return fileDatas[0];
}
bool ExistsFileData(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return false;
}
sqlite3* storage = GetStorage(it->second);
return CountFileData(storage) > 0;
}
void RemoveFileData(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it != m_cacheMap.end())
{
std::string dbName = it->second;
std::error_code ec;
// 删除存该文件内容的数据库
std::filesystem::remove(dbName, ec);
}
// 删除映射信息
RemoveBaseRecord(filePath);
m_cacheMap.erase(filePath);
}
bool AddActionRecord(const std::string& filePath, const ActionRecord& record) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return false;
}
sqlite3* storage = GetStorage(it->second);
InsertActionRecord(storage, record);
return true;
}
bool ExistsActionRecord(const std::string& filePath, const std::string& uuid) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return false;
}
sqlite3* storage = GetStorage(it->second);
return CountActionRecords(storage, uuid) > 0;
}
int ActionRecordCount(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return false;
}
sqlite3* storage = GetStorage(it->second);
return CountActionRecords(storage);
}
std::vector<ActionRecord> RetrieveAllActionRecords(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return {};
}
sqlite3* storage = GetStorage(it->second);
return GetAllActionRecords(storage);
}
void ClearBackup(const std::string& filePath) override
{
auto it = m_cacheMap.find(filePath);
if (it == m_cacheMap.end())
{
return;
}
ClearStorage(it->second);
RemoveBaseRecord(it->first);
m_cacheMap.erase(filePath);
}
private:
sqlite3* MakeBaseStorage() const
{
std::filesystem::path fullPath = m_backupDir / m_dbName;
std::error_code ec;
if (!std::filesystem::exists(m_backupDir))
{
if (!std::filesystem::create_directory(m_backupDir, ec))
{
throw std::runtime_error("Create backup directory failed");
}
}
sqlite3* db = nullptr;
if (sqlite3_open(fullPath.string().c_str(), &db) != SQLITE_OK)
{
throw std::runtime_error("Failed to open database");
}
const char* createTableSQL = R"(
CREATE TABLE IF NOT EXISTS map (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_path TEXT NOT NULL,
db_path TEXT NOT NULL,
create_at TEXT NOT NULL
);
)";
char* errMsg = nullptr;
if (sqlite3_exec(db, createTableSQL, nullptr, nullptr, &errMsg) != SQLITE_OK)
{
std::string err = errMsg ? errMsg : "Unknown error";
sqlite3_free(errMsg);
sqlite3_close(db);
throw std::runtime_error("Failed to create table: " + err);
}
return db;
}
void CreateFileMapInfo(const std::string& filePath)
{
// 对应的数据库文件路径: {备份目录}/{文件名}.{UUID}.sqlite
// 获取文件名(不带后缀)
std::string fileNameStem = std::filesystem::path(filePath).stem().string();
// 生成带 UUID 和新后缀的文件名
std::string newFileName = fileNameStem + "." + GenerateUUID() + ".sqlite";
// 构建数据库文件路径
std::filesystem::path dbPath = m_backupDir / newFileName;
std::string dbNameStr = dbPath.string();
auto createAt = FormatTime(std::chrono::system_clock::now());
const char* insertSQL = R"(
INSERT INTO map (file_path, db_path, create_at) VALUES (?, ?, ?);
)";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(m_baseStorage, insertSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare insert statement");
}
sqlite3_bind_text(stmt, 1, filePath.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, dbNameStr.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, createAt.c_str(), -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to execute insert statement");
}
sqlite3_finalize(stmt);
m_cacheMap[filePath] = dbNameStr;
}
bool ExistFilePath(const std::string& filePath)
{
return m_cacheMap.find(filePath) != m_cacheMap.end();
}
sqlite3* GetStorage(const std::string& dbPath)
{
if (m_cacheStorage.find(dbPath) == m_cacheStorage.end())
{
sqlite3* db = nullptr;
if (sqlite3_open(dbPath.c_str(), &db) != SQLITE_OK)
{
throw std::runtime_error("Failed to open database");
}
const char* createFileDataTableSQL = R"(
CREATE TABLE IF NOT EXISTS filedata (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filepath TEXT NOT NULL,
data BLOB NOT NULL,
create_at TEXT NOT NULL
);
)";
const char* createActionTableSQL = R"(
CREATE TABLE IF NOT EXISTS actions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL,
class_type TEXT NOT NULL,
type TEXT NOT NULL,
data BLOB NOT NULL,
timestamp TEXT NOT NULL,
create_at TEXT NOT NULL
);
)";
char* errMsg = nullptr;
if (sqlite3_exec(db, createFileDataTableSQL, nullptr, nullptr, &errMsg) != SQLITE_OK)
{
std::string err = errMsg ? errMsg : "Unknown error";
sqlite3_free(errMsg);
sqlite3_close(db);
throw std::runtime_error("Failed to create filedata table: " + err);
}
if (sqlite3_exec(db, createActionTableSQL, nullptr, nullptr, &errMsg) != SQLITE_OK)
{
std::string err = errMsg ? errMsg : "Unknown error";
sqlite3_free(errMsg);
sqlite3_close(db);
throw std::runtime_error("Failed to create actions table: " + err);
}
m_cacheStorage.insert({ dbPath, db });
}
return m_cacheStorage.at(dbPath);
}
void ClearStorage(const std::string& dbPath)
{
auto it = m_cacheStorage.find(dbPath);
if (it != m_cacheStorage.end())
{
sqlite3_close(it->second);
m_cacheStorage.erase(it);
}
std::error_code ec;
CString ansiDbpath = Utf8StringToCString(dbPath);
std::filesystem::remove(ansiDbpath.GetBuffer(), ec);
}
std::vector<BaseRecord> GetAllBaseRecords()
{
std::vector<BaseRecord> records;
const char* selectSQL = "SELECT id, file_path, db_path, create_at FROM map";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(m_baseStorage, selectSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare select statement");
}
while (sqlite3_step(stmt) == SQLITE_ROW)
{
BaseRecord record;
record.id = sqlite3_column_int64(stmt, 0);
record.filePath = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
record.dbPath = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
record.createAt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
records.push_back(record);
}
sqlite3_finalize(stmt);
return records;
}
std::vector<FileData> GetAllFileData(sqlite3* db)
{
std::vector<FileData> fileDatas;
const char* selectSQL = "SELECT id, filepath, data, create_at FROM filedata";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, selectSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare select statement");
}
while (sqlite3_step(stmt) == SQLITE_ROW)
{
FileData fileData;
fileData.id = sqlite3_column_int64(stmt, 0);
fileData.filepath = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
const void* data = sqlite3_column_blob(stmt, 2);
int dataSize = sqlite3_column_bytes(stmt, 2);
fileData.data.assign(static_cast<const char*>(data), static_cast<const char*>(data) + dataSize);
fileData.createAt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
fileDatas.push_back(fileData);
}
sqlite3_finalize(stmt);
return fileDatas;
}
bool InsertFileData(sqlite3* db, FileData& fileData)
{
const char* deleteSQL = "DELETE FROM filedata";
char* errMsg = nullptr;
if (sqlite3_exec(db, deleteSQL, nullptr, nullptr, &errMsg) != SQLITE_OK)
{
std::string err = errMsg ? errMsg : "Unknown error";
sqlite3_free(errMsg);
throw std::runtime_error("Failed to delete existing filedata: " + err);
}
const char* insertSQL = "INSERT INTO filedata (filepath, data, create_at) VALUES (?, ?, ?)";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, insertSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare insert statement");
}
sqlite3_bind_text(stmt, 1, fileData.filepath.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 2, fileData.data.data(), static_cast<int>(fileData.data.size()), SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, fileData.createAt.c_str(), -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to execute insert statement");
}
sqlite3_finalize(stmt);
return true;
}
int CountFileData(sqlite3* db)
{
const char* countSQL = "SELECT COUNT(*) FROM filedata";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, countSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare count statement");
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW)
{
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return count;
}
void RemoveBaseRecord(const std::string& filePath)
{
const char* deleteSQL = "DELETE FROM map WHERE file_path = ?";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(m_baseStorage, deleteSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare delete statement");
}
sqlite3_bind_text(stmt, 1, filePath.c_str(), -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to execute delete statement");
}
sqlite3_finalize(stmt);
}
void InsertActionRecord(sqlite3* db, const ActionRecord& record)
{
const char* insertSQL = R"(
INSERT INTO actions (uuid, class_type, type, data, timestamp, create_at)
VALUES (?, ?, ?, ?, ?, ?)
)";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, insertSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare insert statement");
}
sqlite3_bind_text(stmt, 1, record.uuid.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, record.classType.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, record.type.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 4, record.data.data(), static_cast<int>(record.data.size()), SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 5, record.timestamp.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, record.createAt.c_str(), -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
throw std::runtime_error("Failed to execute insert statement");
}
sqlite3_finalize(stmt);
}
int CountActionRecords(sqlite3* db)
{
const char* countSQL = "SELECT COUNT(*) FROM actions";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, countSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare count statement");
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW)
{
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return count;
}
int CountActionRecords(sqlite3* db, const std::string& uuid)
{
const char* countSQL = "SELECT COUNT(*) FROM actions WHERE uuid = ?";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, countSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare count statement");
}
sqlite3_bind_text(stmt, 1, uuid.c_str(), -1, SQLITE_TRANSIENT);
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW)
{
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return count;
}
std::vector<ActionRecord> GetAllActionRecords(sqlite3* db)
{
std::vector<ActionRecord> records;
const char* selectSQL = "SELECT id, uuid, class_type, type, data, timestamp, create_at FROM actions";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, selectSQL, -1, &stmt, nullptr) != SQLITE_OK)
{
throw std::runtime_error("Failed to prepare select statement");
}
while (sqlite3_step(stmt) == SQLITE_ROW)
{
ActionRecord record;
record.id = sqlite3_column_int64(stmt, 0);
record.uuid = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
record.classType = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
record.type = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
const void* data = sqlite3_column_blob(stmt, 4);
int dataSize = sqlite3_column_bytes(stmt, 4);
record.data.assign(static_cast<const char*>(data), static_cast<const char*>(data) + dataSize);
record.timestamp = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
record.createAt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6));
records.push_back(record);
}
sqlite3_finalize(stmt);
return records;
}
private:
std::filesystem::path m_backupDir;
std::string m_dbName;
sqlite3* m_baseStorage;
std::unordered_map<std::string, std::string> m_cacheMap;
std::unordered_map<std::string, sqlite3*> m_cacheStorage;
};
// 工厂类用于创建存储实例
StorageFactory& StorageFactory::GetInstance()
{
static StorageFactory factory;
return factory;
}
/**
* 尽量放到 %appdata% 目录,这是现代的主流玩法,放工作目录,工作目录是变动的,放 exe 所在目录,则有可能没权限,
* 这个目录本身就是用来给 app 存放数据的
* 如果获取失败(按理说不应该失败),则放到工作目录下
*
* \return
*/
static std::filesystem::path GetBackupPath()
{
PWSTR RoamingPath = nullptr;
HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &RoamingPath);
FinalAction finalAction([RoamingPath]() { CoTaskMemFree(RoamingPath); });
if (result == S_OK)
{
std::filesystem::path path = RoamingPath;
path /= "KEVisualization";
path /= "backup";
if (!std::filesystem::exists(path))
{
std::filesystem::create_directories(path);
}
return path;
}
else
{
return "backup";
}
}
std::shared_ptr<IActionStorage> StorageFactory::CreateDefaultStorage()
{
std::filesystem::path backup = GetBackupPath();
return StorageFactory::GetInstance().CreateStorage(backup, "base.sqlite");
}
std::shared_ptr<IActionStorage> StorageFactory::CreateStorage(const std::filesystem::path& backupDir, const std::string& dbName)
{
try
{
std::filesystem::path dbPath = (backupDir / dbName).string();
auto it = m_map.find(dbPath.string());
if (it != m_map.end())
{
return it->second;
}
auto storage = std::make_shared<SQLiteStorage>(backupDir, dbName);
m_map[dbPath.string()] = storage;
return storage;
}
catch (std::runtime_error &e)
{
TRACE("Create storage failed: %s\n", e.what());
return nullptr;
}
}