新增gvf转csv
This commit is contained in:
parent
7bd14d8179
commit
d8759e1318
|
|
@ -23,6 +23,7 @@
|
|||
#include "EnergyScaleDataModel.h"
|
||||
#include "DataCalcProcess/CoincidenceSpectrumProcess.h"
|
||||
#include "BackgroundTaskListModel.h"
|
||||
#include "GvfToCsv/GvfToCsv.h"
|
||||
#include <QDebug>
|
||||
|
||||
using namespace DataProcessWorkPool;
|
||||
|
|
@ -1073,3 +1074,67 @@ bool EnergyScaleaAntiCoincidenceDataTask::processTask()
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
QString GvfToCsvDataTask::GetTaskName()
|
||||
{
|
||||
return QStringLiteral(u"[%1]GVF文件转CSV处理").arg(this->GetProjectName());
|
||||
}
|
||||
|
||||
void GvfToCsvDataTask::setGvfName(const QString& gvfName)
|
||||
{
|
||||
m_gvfName = gvfName;
|
||||
}
|
||||
|
||||
void GvfToCsvDataTask::setCsvName(const QString& csvName)
|
||||
{
|
||||
QDir projectDir(csvName);
|
||||
if (!projectDir.exists()) {
|
||||
projectDir.mkpath(".");
|
||||
}
|
||||
m_csvName = projectDir.filePath("粒子数据_gvf.csv");
|
||||
}
|
||||
|
||||
bool GvfToCsvDataTask::processTask()
|
||||
{
|
||||
QFileInfo csvFileInfo(m_csvName);
|
||||
QDir csvDir = csvFileInfo.absoluteDir();
|
||||
|
||||
if (!csvDir.exists()) {
|
||||
if (!csvDir.mkpath(".")) {
|
||||
m_error = QStringLiteral(u"无法创建输出目录: %1").arg(csvDir.path());
|
||||
LOG_ERROR(m_error.toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString testFile = csvDir.filePath(".test_write.tmp");
|
||||
QFile test(testFile);
|
||||
if (!test.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
m_error = QStringLiteral(u"输出目录不可写: %1").arg(csvDir.path());
|
||||
LOG_ERROR(m_error.toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
test.remove();
|
||||
|
||||
if (m_gvfName.isEmpty() || m_csvName.isEmpty()) {
|
||||
m_error = "GVF文件路径或CSV输出路径为空";
|
||||
LOG_ERROR(m_error.toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<GvfToCsv> gvf2Csv(new GvfToCsv());
|
||||
bool success = gvf2Csv->convertGVF2CSVSync(m_gvfName, m_csvName);
|
||||
|
||||
if (success) {
|
||||
m_resultCsvPath = m_csvName;
|
||||
m_conversionSuccess = true;
|
||||
updateTaskResultData(QVariant(m_resultCsvPath));
|
||||
} else {
|
||||
m_error = gvf2Csv->getLastError();
|
||||
m_conversionSuccess = false;
|
||||
LOG_ERROR(QStringLiteral(u"GVF转换失败: %1")
|
||||
.arg(m_error)
|
||||
.toUtf8().constData());
|
||||
}
|
||||
|
||||
return m_conversionSuccess;
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
#include "AnalysisTypeDefine.h"
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
|
||||
#include <QEventLoop>
|
||||
class EnergyScaleDataModel;
|
||||
class MeasureAnalysisProjectModel;
|
||||
|
||||
|
|
@ -167,6 +167,23 @@ namespace DataProcessWorkPool
|
|||
private:
|
||||
virtual bool processTask() override;
|
||||
};
|
||||
|
||||
class GvfToCsvDataTask : public DataProcessTask
|
||||
{
|
||||
public:
|
||||
QString GetTaskName() override;
|
||||
void setGvfName(const QString& gvfName);
|
||||
void setCsvName(const QString& csvName);
|
||||
QString getError() const { return m_error; }
|
||||
QString getResultCsvPath() const { return m_resultCsvPath; }
|
||||
private:
|
||||
bool processTask() override;
|
||||
QString m_gvfName;
|
||||
QString m_csvName;
|
||||
QString m_resultCsvPath;
|
||||
QString m_error;
|
||||
bool m_conversionSuccess = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DATAPROCESSWORKPOOL_H
|
||||
|
|
|
|||
296
src/GvfToCsv/GvfToCsv.cpp
Normal file
296
src/GvfToCsv/GvfToCsv.cpp
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "GvfToCsv.h"
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
#include <QDir>
|
||||
#include "GlobalDefine.h"
|
||||
GvfToCsv::GvfToCsv(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted,
|
||||
this, &GvfToCsv::onSqliteOperationCompleted);
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated,
|
||||
this, &GvfToCsv::conversionProgress);
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::logMessage,
|
||||
this, [](const QString &msg) { qDebug() << "[GVF转换日志]" << msg; });
|
||||
}
|
||||
|
||||
GvfToCsv::~GvfToCsv()
|
||||
{
|
||||
cleanUp();
|
||||
if (m_syncEventLoop) {
|
||||
delete m_syncEventLoop;
|
||||
}
|
||||
}
|
||||
|
||||
QList<ParticleData> GvfToCsv::parseParticleFrames(const QByteArray &data)
|
||||
{
|
||||
QList<ParticleData> particles;
|
||||
const int minDataSize = GvfConst::HEADER_SIZE + GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
if (data.size() < minDataSize) {
|
||||
qDebug() << "粒子数据长度不足,跳过解析";
|
||||
return particles;
|
||||
}
|
||||
|
||||
const quint8* rawData = reinterpret_cast<const quint8*>(data.constData());
|
||||
if (rawData[0] != GvfConst::HEADER_BYTE1 || rawData[1] != GvfConst::HEADER_BYTE2) {
|
||||
qDebug() << "粒子数据头部校验失败,跳过解析";
|
||||
return particles;
|
||||
}
|
||||
|
||||
const quint8* ptr = rawData + GvfConst::HEADER_SIZE;
|
||||
int remaining = data.size() - GvfConst::HEADER_SIZE;
|
||||
particles.reserve(remaining / GvfConst::PARTICLE_BLOCK_SIZE);
|
||||
|
||||
while (remaining >= GvfConst::PARTICLE_BLOCK_SIZE)
|
||||
{
|
||||
quint16 alignWord = qFromLittleEndian<quint16>(static_cast<const void*>(ptr));
|
||||
quint8 lowByte = alignWord & 0xFF;
|
||||
quint8 highByte = (alignWord >> 8) & 0xFF;
|
||||
|
||||
if (highByte != GvfConst::ALIGN_HIGH_BYTE) {
|
||||
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
const quint8 board = (lowByte >> 4) & 0x0F;
|
||||
const quint8 channel = lowByte & 0x0F;
|
||||
const int channelIndex = board * 4 + channel;
|
||||
|
||||
if (channelIndex >= GvfConst::MAX_CHANNEL_COUNT) {
|
||||
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
quint64 timestamp = 0;
|
||||
const quint8* timestampPtr = ptr + 2;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
timestamp |= static_cast<quint64>(timestampPtr[i]) << (8 * i);
|
||||
}
|
||||
|
||||
const quint16 amplitude = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 8));
|
||||
const quint32 icr = qFromLittleEndian<quint32>(static_cast<const void*>(ptr + 10));
|
||||
const quint16 riseTime = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 14));
|
||||
const quint16 fallTime = qFromLittleEndian<quint16>(static_cast<const void*>(ptr + 16));
|
||||
|
||||
const int address = static_cast<int>(
|
||||
static_cast<double>(amplitude) / GvfConst::AMPLITUDE_MAX * GvfConst::MAX_ADDRESS
|
||||
);
|
||||
|
||||
ParticleData p(
|
||||
board,
|
||||
channel,
|
||||
timestamp,
|
||||
timestamp * GvfConst::TIME_PER_COUNT,
|
||||
amplitude,
|
||||
address,
|
||||
icr,
|
||||
riseTime,
|
||||
fallTime
|
||||
);
|
||||
|
||||
particles.append(p);
|
||||
ptr += GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
remaining -= GvfConst::PARTICLE_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
return particles;
|
||||
}
|
||||
|
||||
void GvfToCsv::cleanUp()
|
||||
{
|
||||
if (m_sqliteWorker) {
|
||||
// 先断开所有信号连接
|
||||
disconnect(m_sqliteWorker.get(), nullptr, this, nullptr);
|
||||
// 停止操作
|
||||
m_sqliteWorker->stopOperation();
|
||||
|
||||
// 等待操作完成
|
||||
QEventLoop loop;
|
||||
QTimer timeoutTimer;
|
||||
timeoutTimer.setSingleShot(true);
|
||||
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted, &loop, &QEventLoop::quit);
|
||||
connect(&timeoutTimer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
|
||||
timeoutTimer.start(3000);
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
m_sqliteWorker.reset();
|
||||
}
|
||||
m_gvfPath.clear();
|
||||
m_csvPath.clear();
|
||||
m_lastError.clear();
|
||||
}
|
||||
|
||||
bool GvfToCsv::convertGVF2CSVSync(const QString &gvfPath, const QString &csvPath)
|
||||
{
|
||||
if (!m_syncEventLoop) {
|
||||
m_syncEventLoop = new QEventLoop();
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
QObject signalReceiver;
|
||||
|
||||
connect(this, &GvfToCsv::conversionFinished, &signalReceiver, [&](bool success) {
|
||||
result = success;
|
||||
m_syncEventLoop->quit();
|
||||
});
|
||||
|
||||
connect(this, &GvfToCsv::errorOccurred, &signalReceiver, [](const QString &msg) {
|
||||
LOG_ERROR(QStringLiteral(u"GVF转换错误: %1").arg(msg).toUtf8().constData());
|
||||
});
|
||||
|
||||
convertGVF2CSVAsync(gvfPath, csvPath);
|
||||
|
||||
m_syncEventLoop->exec();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GvfToCsv::convertGVF2CSVAsync(const QString &gvfPath, const QString &csvPath)
|
||||
{
|
||||
m_lastError.clear();
|
||||
cleanUp();
|
||||
|
||||
if (gvfPath.isEmpty() || csvPath.isEmpty()) {
|
||||
setLastError("GVF/CSV路径不能为空");
|
||||
emit conversionFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo gvfFileInfo(gvfPath);
|
||||
if (!gvfFileInfo.exists() || !gvfFileInfo.isFile()) {
|
||||
setLastError(QString("GVF文件不存在: %1").arg(gvfPath));
|
||||
emit conversionFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo csvFileInfo(csvPath);
|
||||
QDir csvDir = csvFileInfo.absoluteDir();
|
||||
if (!csvDir.exists() && !csvDir.mkpath(".")) {
|
||||
setLastError(QString("无法创建CSV目录: %1").arg(csvDir.path()));
|
||||
emit conversionFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_gvfPath = gvfPath;
|
||||
m_csvPath = csvPath;
|
||||
|
||||
m_sqliteWorker = std::make_unique<SQLiteReadWrite>(this);
|
||||
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::operationCompleted,
|
||||
this, &GvfToCsv::onSqliteOperationCompleted, Qt::QueuedConnection);
|
||||
connect(m_sqliteWorker.get(), &SQLiteReadWrite::progressUpdated,
|
||||
this, &GvfToCsv::conversionProgress, Qt::QueuedConnection);
|
||||
if (!m_sqliteWorker->openDatabase(m_gvfPath)) {
|
||||
setLastError(QString("打开GVF数据库失败: %1").arg(m_gvfPath));
|
||||
emit conversionFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sqliteWorker->startReadWriteOperation();
|
||||
}
|
||||
|
||||
void GvfToCsv::onSqliteOperationCompleted(bool success, const QString &msg)
|
||||
{
|
||||
if (!success) {
|
||||
setLastError(QString("GVF数据读取失败: %1").arg(msg));
|
||||
cleanUp();
|
||||
emit conversionFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
processDBData();
|
||||
emit conversionFinished(true);
|
||||
} catch (const std::exception &e) {
|
||||
setLastError(QString("CSV写入异常: %1").arg(e.what()));
|
||||
emit conversionFinished(false);
|
||||
} catch (...) {
|
||||
setLastError("CSV写入未知异常");
|
||||
emit conversionFinished(false);
|
||||
}
|
||||
|
||||
cleanUp();
|
||||
}
|
||||
|
||||
void GvfToCsv::processDBData()
|
||||
{
|
||||
QVector<DataBaseStruct> dbList = m_sqliteWorker->DataBaseList();
|
||||
if (dbList.isEmpty()) {
|
||||
throw std::runtime_error("GVF数据库中无任何记录");
|
||||
}
|
||||
QFile outFile(m_csvPath);
|
||||
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
throw std::runtime_error(QString("无法创建CSV文件: %1,错误: %2")
|
||||
.arg(m_csvPath)
|
||||
.arg(outFile.errorString())
|
||||
.toStdString());
|
||||
}
|
||||
|
||||
QTextStream out(&outFile);
|
||||
out.setCodec("UTF-8");
|
||||
out << QStringLiteral(u"板卡号,通道号,道址,时间计数\n");
|
||||
|
||||
QString csvBuffer;
|
||||
csvBuffer.reserve(4 * 1024 * 1024); // 4MB缓冲区
|
||||
|
||||
quint64 totalParticles = 0;
|
||||
int emptyFrameCount = 0;
|
||||
int processedFrames = 0;
|
||||
const int totalFrames = dbList.size();
|
||||
|
||||
for (const auto &data : dbList) {
|
||||
QList<ParticleData> particles = parseParticleFrames(data.data);
|
||||
if (particles.isEmpty()) {
|
||||
emptyFrameCount++;
|
||||
} else {
|
||||
totalParticles += particles.size();
|
||||
|
||||
for (const auto &p : particles) {
|
||||
csvBuffer += QString("%1,%2,%3,%4\n")
|
||||
.arg(p.boardId)
|
||||
.arg(p.channelId)
|
||||
.arg(p.address)
|
||||
.arg(p.timestampCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (csvBuffer.size() >= 4 * 1024 * 1024) {
|
||||
out << csvBuffer;
|
||||
csvBuffer.clear();
|
||||
}
|
||||
|
||||
processedFrames++;
|
||||
if (processedFrames % 1000 == 0) {
|
||||
int remainingFrames = totalFrames - processedFrames;
|
||||
int progress = (processedFrames * 100) / totalFrames;
|
||||
emit conversionProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (!csvBuffer.isEmpty()) {
|
||||
out << csvBuffer;
|
||||
}
|
||||
|
||||
out.flush();
|
||||
outFile.close();
|
||||
|
||||
if (outFile.error() != QFile::NoError) {
|
||||
throw std::runtime_error(QString("CSV文件写入失败: %1,错误: %2")
|
||||
.arg(m_csvPath)
|
||||
.arg(outFile.errorString())
|
||||
.toStdString());
|
||||
}
|
||||
|
||||
if (totalParticles == 0) {
|
||||
QFile::remove(m_csvPath);
|
||||
throw std::runtime_error(QString("GVF文件中未解析到任何有效粒子数据,空帧数: %1")
|
||||
.arg(emptyFrameCount)
|
||||
.toStdString());
|
||||
}
|
||||
emit conversionProgress(100);
|
||||
}
|
||||
88
src/GvfToCsv/GvfToCsv.h
Normal file
88
src/GvfToCsv/GvfToCsv.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef GVFTOCSV_H
|
||||
#define GVFTOCSV_H
|
||||
|
||||
#include "sqliteread.h"
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <memory>
|
||||
#include <QDebug>
|
||||
#include <QtCore>
|
||||
#include <QtEndian>
|
||||
// 常量定义:替换魔法数,提升可读性和可维护性
|
||||
namespace GvfConst {
|
||||
constexpr quint8 HEADER_BYTE1 = 0xA7; // 头部固定字节1
|
||||
constexpr quint8 HEADER_BYTE2 = 0xA2; // 头部固定字节2
|
||||
constexpr int HEADER_SIZE = 3; // 头部总字节数
|
||||
constexpr int PARTICLE_BLOCK_SIZE = 20; // 单个粒子数据块大小
|
||||
constexpr quint8 ALIGN_HIGH_BYTE = 0xFE; // 对齐标记高字节
|
||||
constexpr int MAX_CHANNEL_COUNT = 32; // 硬件最大通道数
|
||||
constexpr double CLOCK_FREQ_MHZ = 200.0; // 时钟频率(200MHz)
|
||||
constexpr double TIME_PER_COUNT = 1.0 / (CLOCK_FREQ_MHZ * 1e6); // 单计数时间(5ns)
|
||||
constexpr int MAX_ADDRESS = 4096; // 最大道址
|
||||
constexpr int AMPLITUDE_MAX = 65535; // 幅度最大值
|
||||
}
|
||||
|
||||
struct ParticleData {
|
||||
int boardId = 0; // 板卡号 (0-15)
|
||||
int channelId = 0; // 通道号 (0-15)
|
||||
quint64 timestampCount = 0; // 原始时间计数(6字节)
|
||||
double timeSeconds = 0.0; // 换算后的时间(秒)
|
||||
quint16 amplitude = 0; // 原始幅度值 (0-65535)
|
||||
int address = 0; // 转换后的道址
|
||||
quint32 icr = 0; // 输入计数率相关值
|
||||
quint16 riseTime = 0; // 上升时间
|
||||
quint16 fallTime = 0; // 下降时间
|
||||
|
||||
// 添加构造函数
|
||||
ParticleData() = default; // 保留默认构造
|
||||
ParticleData(int board, int ch, quint64 ts, double ts_sec,
|
||||
quint16 amp, int addr, quint32 icr_val,
|
||||
quint16 rise, quint16 fall)
|
||||
: boardId(board)
|
||||
, channelId(ch)
|
||||
, timestampCount(ts)
|
||||
, timeSeconds(ts_sec)
|
||||
, amplitude(amp)
|
||||
, address(addr)
|
||||
, icr(icr_val)
|
||||
, riseTime(rise)
|
||||
, fallTime(fall)
|
||||
{}
|
||||
};
|
||||
class GvfToCsv : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GvfToCsv(QObject *parent = nullptr);
|
||||
~GvfToCsv() override;
|
||||
|
||||
void convertGVF2CSVAsync(const QString &gvfPath, const QString &csvPath);
|
||||
// 添加同步转换方法
|
||||
bool convertGVF2CSVSync(const QString &gvfPath, const QString &csvPath);
|
||||
// 添加错误信息获取
|
||||
QString getLastError() const { return m_lastError; }
|
||||
|
||||
signals:
|
||||
void conversionProgress(int percent);
|
||||
void conversionFinished(bool success);
|
||||
void errorOccurred(const QString &msg);
|
||||
|
||||
private slots:
|
||||
void onSqliteOperationCompleted(bool success, const QString &msg);
|
||||
|
||||
private:
|
||||
void processDBData();
|
||||
QList<ParticleData> parseParticleFrames(const QByteArray &data);
|
||||
void cleanUp();
|
||||
// 添加错误信息存储
|
||||
void setLastError(const QString& error) { m_lastError = error; emit errorOccurred(error); }
|
||||
|
||||
std::unique_ptr<SQLiteReadWrite> m_sqliteWorker;
|
||||
QString m_gvfPath;
|
||||
QString m_csvPath;
|
||||
QString m_lastError;
|
||||
// 用于同步转换
|
||||
QEventLoop* m_syncEventLoop = nullptr;
|
||||
};
|
||||
#endif // GVFTOCSV_H
|
||||
276
src/GvfToCsv/sqliteread.cpp
Normal file
276
src/GvfToCsv/sqliteread.cpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#include "sqliteread.h"
|
||||
#include <QtConcurrent>
|
||||
SQLiteReadWrite::SQLiteReadWrite(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_connectionName = QString("SQLiteConnection_%1").arg(reinterpret_cast<quintptr>(this));
|
||||
idValue = 0;
|
||||
m_stopRequested = false;
|
||||
|
||||
}
|
||||
|
||||
SQLiteReadWrite::~SQLiteReadWrite()
|
||||
{
|
||||
if (!m_isClosed) {
|
||||
closeDatabase();
|
||||
}
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
}
|
||||
|
||||
bool SQLiteReadWrite::openDatabase(const QString &dbPath)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
if (!m_isClosed) {
|
||||
closeDatabase();
|
||||
}
|
||||
|
||||
// 确定数据库路径
|
||||
QString path = dbPath;
|
||||
if (path.isEmpty()) {
|
||||
QString appDir = QCoreApplication::applicationDirPath();
|
||||
path = QDir(appDir).filePath("test_readwrite.db");
|
||||
}
|
||||
|
||||
m_dbPath = path;
|
||||
|
||||
// 打开数据库
|
||||
m_database = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
|
||||
m_database.setDatabaseName(m_dbPath);
|
||||
|
||||
if (!m_database.open()) {
|
||||
log(QString("打开数据库失败: %1").arg(m_database.lastError().text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 启用外键和WAL模式(提高并发性能)
|
||||
QSqlQuery query(m_database);
|
||||
// 开启外键约束
|
||||
if (!query.exec("PRAGMA foreign_keys = ON;")) {
|
||||
log(QString("启用外键失败: %1").arg(query.lastError().text()));
|
||||
}
|
||||
|
||||
// 开启WAL模式(写时复制,提高读写并发性能)
|
||||
if (!query.exec("PRAGMA journal_mode = WAL;")) {
|
||||
log(QString("启用WAL模式失败: %1").arg(query.lastError().text()));
|
||||
}
|
||||
|
||||
// // 设置同步模式(在安全性和性能之间权衡)
|
||||
// if (!query.exec("PRAGMA synchronous = NORMAL;")) {
|
||||
// log(QString("设置同步模式失败: %1").arg(query.lastError().text()));
|
||||
// }
|
||||
|
||||
// 设置缓存大小
|
||||
if (!query.exec("PRAGMA cache_size = 10000;")) {
|
||||
log(QString("设置缓存大小失败: %1").arg(query.lastError().text()));
|
||||
}
|
||||
|
||||
m_isClosed = false;
|
||||
log(QString("数据库打开成功: %1").arg(m_dbPath));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SQLiteReadWrite::closeDatabase()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
if (m_isClosed) {
|
||||
return;
|
||||
}
|
||||
m_isClosed = true;
|
||||
|
||||
if (m_database.isOpen()) {
|
||||
m_database.close();
|
||||
log("数据库已关闭");
|
||||
}
|
||||
if (QSqlDatabase::contains(m_connectionName)) {
|
||||
QSqlDatabase::removeDatabase(m_connectionName);
|
||||
}
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
|
||||
void SQLiteReadWrite::startReadWriteOperation()
|
||||
{
|
||||
if (!m_database.isOpen()) {
|
||||
emit operationCompleted(false, "数据库未打开");
|
||||
return;
|
||||
}
|
||||
|
||||
m_stopRequested = false;
|
||||
//在单独的线程中执行,避免阻塞UI
|
||||
QtConcurrent::run(this, &SQLiteReadWrite::processReadWrite);
|
||||
}
|
||||
|
||||
void SQLiteReadWrite::stopOperation()
|
||||
{
|
||||
m_stopRequested = true;
|
||||
log("stop requese send");
|
||||
}
|
||||
|
||||
void SQLiteReadWrite::processReadWrite()
|
||||
{
|
||||
m_timer.start();
|
||||
log("开始边读边写操作...");
|
||||
try {
|
||||
processWithMultipleConnections();
|
||||
if (!m_stopRequested) {
|
||||
qint64 elapsed = m_timer.elapsed();
|
||||
QString message = QString("操作完成,总耗时: %1 毫秒").arg(elapsed);
|
||||
log(message);
|
||||
emit operationCompleted(true, message);
|
||||
} else {
|
||||
emit operationCompleted(false, "quxiao");
|
||||
}
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
log(QString("发生异常: %1").arg(e.what()));
|
||||
emit operationCompleted(false, QString("异常: %1").arg(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SQLiteReadWrite::processWithMultipleConnections()
|
||||
{
|
||||
const QString readConnName = m_connectionName + "_read";
|
||||
|
||||
int totalRecords = 0;
|
||||
int processedRecords = 0;
|
||||
bool readSuccess = false;
|
||||
|
||||
{
|
||||
QSqlDatabase readDb = QSqlDatabase::addDatabase("QSQLITE", readConnName);
|
||||
readDb.setDatabaseName(m_dbPath);
|
||||
|
||||
if (!readDb.open()) {
|
||||
QString logMsg = QString("打开读取连接失败: %1").arg(readDb.lastError().text());
|
||||
log(logMsg.toUtf8().constData());
|
||||
emit operationCompleted(false, "无法打开数据库连接");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QSqlQuery optimizeQuery(readDb);
|
||||
optimizeQuery.exec("PRAGMA journal_mode = WAL;");
|
||||
optimizeQuery.exec("PRAGMA synchronous = OFF;");
|
||||
optimizeQuery.exec("PRAGMA cache_size = -200000;"); // 200MB缓存
|
||||
optimizeQuery.exec("PRAGMA temp_store = MEMORY;");
|
||||
optimizeQuery.exec("PRAGMA mmap_size = 2147483648;"); // 2GB内存映射
|
||||
optimizeQuery.exec("PRAGMA query_only = ON;");
|
||||
optimizeQuery.finish();
|
||||
}
|
||||
|
||||
{
|
||||
QSqlQuery countQuery(readDb);
|
||||
if (countQuery.exec("SELECT COUNT(*) FROM lmdatainfov2")) {
|
||||
countQuery.next();
|
||||
totalRecords = countQuery.value(0).toInt();
|
||||
}
|
||||
countQuery.finish();
|
||||
}
|
||||
|
||||
if (totalRecords == 0) {
|
||||
log("数据库中无记录");
|
||||
readDb.close();
|
||||
emit operationCompleted(false, "数据库中无有效数据");
|
||||
return;
|
||||
}
|
||||
|
||||
QString totalMsg = QString("GVF数据库总记录数: %1 条").arg(totalRecords);
|
||||
log(totalMsg.toUtf8().constData());
|
||||
|
||||
const int pageSize = 200;
|
||||
int lastId = 0;
|
||||
|
||||
m_DataBaseList.clear();
|
||||
m_DataBaseList.reserve(totalRecords);
|
||||
|
||||
while (!m_stopRequested) {
|
||||
QSqlQuery readQuery(readDb);
|
||||
readQuery.setForwardOnly(true);
|
||||
readQuery.prepare("SELECT id, data FROM lmdatainfov2 WHERE id > :lastId ORDER BY id LIMIT :limit");
|
||||
readQuery.bindValue(":lastId", lastId);
|
||||
readQuery.bindValue(":limit", pageSize);
|
||||
|
||||
if (!readQuery.exec()) {
|
||||
QString errMsg = QString("数据读取失败: %1").arg(readQuery.lastError().text());
|
||||
log(errMsg.toUtf8().constData());
|
||||
readQuery.finish();
|
||||
break;
|
||||
}
|
||||
|
||||
int pageRecords = 0;
|
||||
while (readQuery.next() && !m_stopRequested) {
|
||||
DataBaseStruct data;
|
||||
data.id = readQuery.value(0).toInt();
|
||||
data.data = readQuery.value(1).toByteArray();
|
||||
m_DataBaseList.append(data);
|
||||
lastId = data.id;
|
||||
pageRecords++;
|
||||
}
|
||||
|
||||
readQuery.finish();
|
||||
readQuery.clear();
|
||||
|
||||
if (pageRecords == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
processedRecords += pageRecords;
|
||||
int remainingRecords = totalRecords - processedRecords;
|
||||
int progress = (processedRecords * 100) / totalRecords;
|
||||
emit progressUpdated(progress, processedRecords, totalRecords);
|
||||
}
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
readDb.close();
|
||||
readSuccess = true;
|
||||
}
|
||||
|
||||
QThread::msleep(50);
|
||||
|
||||
if (QSqlDatabase::contains(readConnName)) {
|
||||
QSqlDatabase::removeDatabase(readConnName);
|
||||
}
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QSqlDatabase SQLiteReadWrite::createConnection()
|
||||
{
|
||||
QString connName = QString("TempConnection_%1_%2")
|
||||
.arg(m_connectionName)
|
||||
.arg(QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connName);
|
||||
db.setDatabaseName(m_dbPath);
|
||||
|
||||
if (!db.open()) {
|
||||
log(QString("创建临时连接失败: %1").arg(db.lastError().text()));
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
void SQLiteReadWrite::log(const QString &message)
|
||||
{
|
||||
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
|
||||
QString logMsg = QString("[%1] %2").arg(timestamp).arg(message);
|
||||
|
||||
emit logMessage(logMsg);
|
||||
}
|
||||
|
||||
QVector<DataBaseStruct> SQLiteReadWrite::DataBaseList() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_DataBaseList;
|
||||
}
|
||||
|
||||
void SQLiteReadWrite::setIdValue(int value)
|
||||
{
|
||||
idValue = value;
|
||||
}
|
||||
89
src/GvfToCsv/sqliteread.h
Normal file
89
src/GvfToCsv/sqliteread.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// sqlitereadwrite.h
|
||||
#ifndef SQLITERREADWRITE_H
|
||||
#define SQLITERREADWRITE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QAtomicInteger>
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
struct DataBaseStruct
|
||||
{
|
||||
int id;//ID
|
||||
int curdevconfigindex;
|
||||
int orgdevconfigindex;
|
||||
int icr;
|
||||
int datanum;
|
||||
int tick;
|
||||
QByteArray data;
|
||||
QString reserve;
|
||||
};
|
||||
|
||||
class SQLiteReadWrite : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SQLiteReadWrite(QObject *parent = nullptr);
|
||||
~SQLiteReadWrite();
|
||||
|
||||
bool openDatabase(const QString &dbPath);
|
||||
void closeDatabase();
|
||||
|
||||
// 边读边写操作
|
||||
void startReadWriteOperation();
|
||||
void stopOperation();
|
||||
|
||||
void setIdValue(int value);
|
||||
|
||||
QVector<DataBaseStruct> DataBaseList() const;
|
||||
|
||||
signals:
|
||||
void progressUpdated(int percent, qint64 processed, qint64 total);
|
||||
void operationCompleted(bool success, const QString &message);
|
||||
void logMessage(const QString &message);
|
||||
void emitReadId(int readId);
|
||||
void emitStopTime();
|
||||
|
||||
private slots:
|
||||
void processReadWrite();
|
||||
|
||||
private:
|
||||
// 数据库连接管理
|
||||
QSqlDatabase m_database;
|
||||
QString m_dbPath;
|
||||
|
||||
// 线程安全
|
||||
mutable QMutex m_mutex;
|
||||
QAtomicInteger<bool> m_stopRequested{false};
|
||||
|
||||
// 性能统计
|
||||
QElapsedTimer m_timer;
|
||||
|
||||
// 连接名(确保多线程连接唯一性)
|
||||
QString m_connectionName;
|
||||
|
||||
// 私有方法
|
||||
QSqlDatabase createConnection();
|
||||
|
||||
|
||||
// 具体操作
|
||||
void processWithMultipleConnections();
|
||||
|
||||
void log(const QString &message);
|
||||
|
||||
int idValue;
|
||||
|
||||
QVector<DataBaseStruct> m_DataBaseList;
|
||||
|
||||
bool m_isClosed = true;
|
||||
|
||||
};
|
||||
|
||||
#endif // SQLITERREADWRITE_H
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>892</width>
|
||||
<height>23</height>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_measurement_analysis">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <QFileInfo>
|
||||
#include <QTimer>
|
||||
#include "DataProcessWorkPool.h"
|
||||
|
||||
#include "GlobalDefine.h"
|
||||
NewMeasureAnalysisDlg::NewMeasureAnalysisDlg(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::NewMeasureAnalysisDlg)
|
||||
|
|
@ -242,17 +242,33 @@ void NewMeasureAnalysisDlg::on_btn_ok_clicked()
|
|||
QMessageBox::warning(this, QStringLiteral(u"警告"), QStringLiteral(u"创建测量分析项目工作目录失败:\n%1!").arg(project_dir_path));
|
||||
return;
|
||||
}
|
||||
if ( ui->checkBox_file_data->isChecked() ) {
|
||||
if ( ui->checkBox_file_data->isChecked() )
|
||||
{
|
||||
const QString& data_file_path = ui->lineEdit_filename->property("data_file_path").toString();
|
||||
if (data_file_path.isEmpty()) {
|
||||
QMessageBox::warning(this, QStringLiteral(u"警告"), QStringLiteral(u"请选择粒子数据文件!"));
|
||||
return;
|
||||
}
|
||||
auto separate_task = new DataProcessWorkPool::ParticleDataSortByMinimysTask;
|
||||
separate_task->SetAllChannelParticleDataFilename(data_file_path);
|
||||
separate_task->SetSortedResultDir(project_dir_path);
|
||||
separate_task->SetFinishedNotifier(this, "onNewProjectFromFileFinished", project_name);
|
||||
separate_task->StartTask();
|
||||
|
||||
if(QFileInfo(data_file_path).suffix().toLower() == "gvf")
|
||||
{
|
||||
// 正确使用任务池处理GVF转换
|
||||
auto gvfTask = new DataProcessWorkPool::GvfToCsvDataTask;
|
||||
gvfTask->setGvfName(data_file_path);
|
||||
gvfTask->setCsvName(project_dir_path);
|
||||
gvfTask->SetFinishedNotifier(this, "onGvfConversionFinished", project_name);
|
||||
gvfTask->StartTask();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接处理CSV文件
|
||||
startParticleSortTask(data_file_path, project_name, project_dir_path);
|
||||
}
|
||||
// auto separate_task = new DataProcessWorkPool::ParticleDataSortByMinimysTask;
|
||||
// separate_task->SetAllChannelParticleDataFilename(data_file_path);
|
||||
// separate_task->SetSortedResultDir(project_dir_path);
|
||||
// separate_task->SetFinishedNotifier(this, "onNewProjectFromFileFinished", project_name);
|
||||
// separate_task->StartTask();
|
||||
|
||||
ui->stackedWidget->setEnabled(false);
|
||||
ui->label_note->setEnabled(false);
|
||||
|
|
@ -274,3 +290,59 @@ void NewMeasureAnalysisDlg::on_btn_ok_clicked()
|
|||
// separate_task->SetFinishedNotifier(this->_tree_measure_analysis, "onFinishedSeparateEveryChannelParticleData", project_name);
|
||||
// separate_task->StartTask();
|
||||
}
|
||||
|
||||
// 添加新的槽函数处理GVF转换完成
|
||||
void NewMeasureAnalysisDlg::onGvfConversionFinished(bool ok, const QString& project_name, const QVariant &data)
|
||||
{
|
||||
this->_task_wait_timer->stop();
|
||||
ui->progressBar->setValue(100);
|
||||
|
||||
if (ok) {
|
||||
QString csvFilePath = data.toString();
|
||||
QFileInfo fileInfo(csvFilePath);
|
||||
|
||||
if (fileInfo.exists() && fileInfo.size() > 100) {
|
||||
startParticleSortTask(csvFilePath, project_name, fileInfo.absolutePath());
|
||||
return;
|
||||
} else {
|
||||
ok = false;
|
||||
QString errorMsg;
|
||||
if (!fileInfo.exists()) {
|
||||
errorMsg = QStringLiteral(u"输出文件不存在: %1").arg(csvFilePath);
|
||||
} else {
|
||||
errorMsg = QStringLiteral(u"输出文件为空: %1").arg(csvFilePath);
|
||||
QFile::remove(csvFilePath);
|
||||
}
|
||||
LOG_ERROR(errorMsg.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
QString projects_dir_path = QDir(qApp->applicationDirPath()).filePath("Projects");
|
||||
QString project_dir_path = QDir(projects_dir_path).filePath(project_name);
|
||||
QDir project_dir(project_dir_path);
|
||||
if (project_dir.exists()) {
|
||||
project_dir.removeRecursively();
|
||||
}
|
||||
|
||||
QMessageBox::critical(this, QStringLiteral(u"GVF转换失败"),
|
||||
QStringLiteral(u"错误信息: %1\n\n请检查GVF文件格式是否正确。")
|
||||
.arg(data.isValid() ? data.toString() : "未知错误"));
|
||||
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->stackedWidget->setEnabled(true);
|
||||
ui->label_note->setEnabled(true);
|
||||
ui->plainTextEdit_description->setEnabled(true);
|
||||
ui->btn_previous_step->setEnabled(true);
|
||||
ui->btn_next_step->setEnabled(true);
|
||||
ui->btn_ok->setEnabled(true);
|
||||
}
|
||||
|
||||
void NewMeasureAnalysisDlg::startParticleSortTask(const QString& data_file_path,
|
||||
const QString& project_name,
|
||||
const QString& project_dir_path)
|
||||
{
|
||||
auto separate_task = new DataProcessWorkPool::ParticleDataSortByMinimysTask;
|
||||
separate_task->SetAllChannelParticleDataFilename(data_file_path);
|
||||
separate_task->SetSortedResultDir(project_dir_path);
|
||||
separate_task->SetFinishedNotifier(this, "onNewProjectFromFileFinished", project_name);
|
||||
separate_task->StartTask();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,13 @@ public:
|
|||
private:
|
||||
void initialization();
|
||||
void newProject(const QString &particle_data_filename = QString());
|
||||
|
||||
void startParticleSortTask(const QString& data_file_path,
|
||||
const QString& project_name,
|
||||
const QString& project_dir_path);
|
||||
private slots:
|
||||
void onNewProjectFromFileFinished(bool ok, const QString& project_name, const QVariant &data);
|
||||
void on_btn_ok_clicked();
|
||||
void onGvfConversionFinished(bool ok, const QString& project_name, const QVariant &data);
|
||||
|
||||
private:
|
||||
Ui::NewMeasureAnalysisDlg *ui;
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ SOURCES += \
|
|||
EnergyCountPlotView/EnergyCountPlotView.cpp \
|
||||
EnergyScaleDataModel.cpp \
|
||||
EnergyScaleForm.cpp \
|
||||
GvfToCsv/GvfToCsv.cpp \
|
||||
GvfToCsv/sqliteread.cpp \
|
||||
MainWindow.cpp \
|
||||
MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.cpp \
|
||||
MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.cpp \
|
||||
|
|
@ -136,6 +138,8 @@ HEADERS += \
|
|||
EnergyScaleDataModel.h \
|
||||
EnergyScaleForm.h \
|
||||
GlobalDefine.h \
|
||||
GvfToCsv/GvfToCsv.h \
|
||||
GvfToCsv/sqliteread.h \
|
||||
MainWindow.h \
|
||||
MeasureAnalysisDataTableView/MeasureAnalysisDataTableView.h \
|
||||
MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.h \
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user