update LibreCAL API from LibreCAL repository

This commit is contained in:
Jan Käberich 2025-02-19 12:34:43 +01:00
parent ef8cdeccd7
commit c56fdf0ce5
14 changed files with 10748 additions and 10 deletions

View file

@ -1,12 +1,54 @@
#include "caldevice.h"
#include "Util/util.h"
#include "ui_factoryUpdateDialog.h"
#include "Util/QMicroz/qmicroz.h"
#include "CustomWidgets/informationbox.h"
#include <QDebug>
#include <QDateTime>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>
#include <QFile>
#include <QFileDialog>
using namespace std;
static const QStringList factoryProblemSerials = {
"5mNkR0s+NzMA", "5mNkR0s2DTcA", "5mNkR0s2gzcA", "5mNkR0s2HTMA", "5mNkR0s2STMA",
"5mNkR0s3pzcA", "5mNkR0s8ijcA", "5mNkR0s9jjMA", "5mNkR0sGdCsA", "5mNkR0slqjEA",
"5mNkR0srVDMA", "5mNkR0sssjcA", "5mNkR0suHDcA", "5mNkR0svOTcA", "5mNkR0svrzcA",
"5mNkR0swkTcA", "5mNkR0s_JzMA", "5mNkR0t+HTUA", "5mNkR0t+JzcA", "5mNkR0t+mjUA",
"5mNkR0t0IzIA", "5mNkR0t0TjAA", "5mNkR0t1bTAA", "5mNkR0t1cjUA", "5mNkR0t1HDAA",
"5mNkR0t2oTIA", "5mNkR0t2rTAA", "5mNkR0t2uyoA", "5mNkR0t3HC0A", "5mNkR0t3HjIA",
"5mNkR0t3MS0A", "5mNkR0t3vC0A", "5mNkR0t3VzUA", "5mNkR0t4aS0A", "5mNkR0t4nDUA",
"5mNkR0t5fDcA", "5mNkR0t6cDcA", "5mNkR0t9kzcA", "5mNkR0t9OTcA", "5mNkR0t9UTUA",
"5mNkR0tbPTUA", "5mNkR0tbqTUA", "5mNkR0tcHzUA", "5mNkR0tctDUA", "5mNkR0tcwjcA",
"5mNkR0tdrDUA", "5mNkR0telDUA", "5mNkR0tgaTkA", "5mNkR0tgRzcA", "5mNkR0tgWTkA",
"5mNkR0tikTUA", "5mNkR0tivDcA", "5mNkR0tIXS8A", "5mNkR0tLOTkA", "5mNkR0tqtTkA",
"5mNkR0tshS0A", "5mNkR0tsnioA", "5mNkR0tSOzQA", "5mNkR0tSqjQA", "5mNkR0tThjUA",
"5mNkR0tTxTUA", "5mNkR0tuVS0A", "5mNkR0tvGC0A", "5mNkR0tvYyoA", "5mNkR0tWljIA",
"5mNkR0tXMDIA", "5mNkR0t_UzUA", "5mNkR0uBiC0A", "5mNkR0uBNS0A", "5mNkR0uCPTYA",
"5mNkR0uCrTYA", "5mNkR0uDgDIA", "5mNkR0uFjTUA", "5mNkR0uFTjUA", "5mNkR0ugbCoA",
"5mNkR0uGIy0A", "5mNkR0uGkzUA", "5mNkR0uGpi0A", "5mNkR0uGRC0A", "5mNkR0uGrjUA",
"5mNkR0uGSzUA", "5mNkR0uGVCoA", "5mNkR0uHIyoA", "5mNkR0uHJjUA", "5mNkR0uHmC0A",
"5mNkR0uHpzUA", "5mNkR0uHTzUA", "5mNkR0uIdzUA", "5mNkR0uIPDYA", "5mNkR0uIpy0A",
"5mNkR0uIqioA", "5mNkR0uISioA", "5mNkR0uITjUA", "5mNkR0uIVi0A", "5mNkR0uJejYA",
"5mNkR0uJSDUA", "5mNkR0uKNDYA", "5mNkR0uKnzYA", "5mNkR0uLfTIA", "5mNkR0uLPjIA",
"5mNkR0uMbTIA", "5mNkR0uNpy0A", "5mNkR0uOTzAA", "5mNkR0uPhTAA", "5mOUNss0EysA",
"5mOUNss1XisA", "5mOUNsseICwA", "5mOUNssiNiwA", "5mOUNssnISwA", "5mOUNssnIyUA",
"5mOUNssrmCQA", "5mOUNssRmicA", "5mOUNssUlycA", "5mOUNssURCcA", "5mOUNssvjyQA",
"5mOUNssvNSUA", "5mOUNssVXSwA", "5mOUNsszOiQA", "5mOUNst9liQA", "5mOUNstAhSsA",
"5mOUNstFSysA", "5mOUNstFwCsA", "5mOUNstGmCUA", "5mOUNstHaCUA", "5mOUNstJkScA",
"5mOUNstOxysA", "5mOUNstsYiIA", "5mOUNstYRi0A", "5mOUNsuHqioA", "5mOUNsuhVSgA",
"5mOUNsuJLSoA", "5mOUNsuJrioA", "5mOUNsuVdCcA", "5mNkR0s1sjMA", "5mNkR0s3MjMA",
"5mNkR0s3XDcA", "5mNkR0slWjEA", "5mNkR0sxkC8A", "5mNkR0t+bzcA", "5mNkR0t1azIA",
"5mNkR0taKzUA", "5mNkR0tbbTcA", "5mNkR0tgGTkA", "5mNkR0thGDcA", "5mNkR0thiTcA",
"5mNkR0thKDcA", "5mNkR0tRYzQA", "5mNkR0tWBTIA", "5mNkR0uCpDYA", "5mNkR0uDXDYA",
"5mNkR0uJUDUA", "5mNkR0uLfjYA", "5mNkR0uMfjIA",
};
static QString getLocalDateTimeWithUtcOffset()
{
QDateTime currentDateTime = QDateTime::currentDateTime();
@ -35,6 +77,7 @@ CalDevice::CalDevice(QString serial) :
usb(new USBDevice(serial))
{
loadThread = nullptr;
transferActive = false;
// Check device identification
auto id = usb->Query("*IDN?");
@ -66,6 +109,74 @@ CalDevice::CalDevice(QString serial) :
numPorts = 0;
}
connect(usb, &USBDevice::communicationFailure, this, &CalDevice::disconnected);
connect(this, &CalDevice::updateCoefficientsDone, this, [=]() {
transferActive = false;
});
// Check if this device was affected by the factory calibration problem
if(factoryProblemSerials.contains(usb->serial())) {
// it was, check if it still has bad factory coefficients
struct point {
uint16_t pointNum;
double freq;
};
std::array<point, 3> pointsToCheck = {{
{.pointNum = 451, .freq = 1.0},
{.pointNum = 551, .freq = 2.0},
{.pointNum = 651, .freq = 3.0},
}};
bool hasBadData = false;
for(auto point : pointsToCheck) {
// grab the point (just checking P12_THROUGH is enough
QString ret = usb->Query(":COEFF:GET? FACTORY P12_THROUGH "+QString::number(point.pointNum));
auto parts = ret.split(",");
if (parts.size() == 9 && parts[0].toDouble() == point.freq) {
// got the correct data, check S12 phase
auto S12 = std::complex(parts[3].toDouble(), parts[4].toDouble());
auto constexpr expectedS12Delay = 498e-12;
auto expectedS12Phase = expectedS12Delay * (point.freq * 1e9) * 2*M_PI;
auto phase = -arg(S12);
// unwrap to expected phase
while(phase < expectedS12Phase - M_PI) {
phase += 2*M_PI;
}
double S12Delay = phase / (2*M_PI) / (point.freq * 1e9);
qDebug() << "expected delay:" << expectedS12Delay << "factory calibration delay:" << S12Delay;
auto error = S12Delay - expectedS12Delay;
double tolerance = 17e-12;
if(abs(error) > tolerance) {
hasBadData = true;
break;
}
// QList<double> possibleErrors({0, -94.7e-12, 34.8e-12});
// for(auto e : possibleErrors) {
// if(abs(error-e) <= tolerance) {
// // this is the error of the LibreCAL
// if(e != 0) {
// // still has the wrong coefficients
// hasBadData = true;
// }
// break;
// }
// }
} else {
qWarning() << "Unexpected point frequency, factory calibration data is dubious";
}
if(hasBadData) {
break;
}
}
if(hasBadData) {
if(InformationBox::AskQuestion("Update Factory Coefficients?", "Your LibreCAL with serial number "+usb->serial()+" is affected by "
"a mistake in the factory calibration and its factory coefficients (specifically the phase of all "
"THRU standard definitions) are slightly wrong. The mistake has been corrected and updated factory "
"coefficients are available. Do you want to update the factory coefficients now?", false)) {
factoryUpdateDialog();
}
}
}
}
CalDevice::~CalDevice()
@ -186,6 +297,7 @@ void CalDevice::loadCoefficientSets(QStringList names, QList<int> ports, bool fa
}
abortLoading = false;
transferActive = true;
if(fast && Util::firmwareEqualOrHigher(firmware, "0.2.1")) {
loadThread = new std::thread(&CalDevice::loadCoefficientSetsThreadFast, this, names, ports);
} else {
@ -199,6 +311,7 @@ void CalDevice::abortCoefficientLoading()
abortLoading = true;
loadThread->join();
loadThread = nullptr;
transferActive = false;
}
}
@ -208,6 +321,7 @@ void CalDevice::saveCoefficientSets()
// nothing to do, already done
emit updateCoefficientsDone(true);
} else {
transferActive = true;
new std::thread(&CalDevice::saveCoefficientSetsThread, this);
}
}
@ -356,11 +470,11 @@ void CalDevice::loadCoefficientSetsThreadFast(QStringList names, QList<int> port
QString line;
if(!usb->receive(&line)) {
// failed to receive something, abort
return c;
return nullptr;
}
if(line.startsWith("ERROR")) {
// something went wront
return c;
return nullptr;
}
// ignore start, comments and option line
if(line.startsWith("START") || line.startsWith("!") || line.startsWith("#")) {
@ -388,7 +502,7 @@ void CalDevice::loadCoefficientSetsThreadFast(QStringList names, QList<int> port
}
c->t.AddDatapoint(p);
} catch (...) {
return c;
return nullptr;
}
}
};
@ -523,19 +637,34 @@ void CalDevice::saveCoefficientSetsThread()
for(int i=1;i<=numPorts;i++) {
if(set.opens.count(i)) {
success &= createCoefficient(set.name, "P"+QString::number(i)+"_OPEN", set.opens[i]->t, set.opens[i]->modified);
if(!success) {
break;
}
}
if(set.shorts.count(i)) {
success &= createCoefficient(set.name, "P"+QString::number(i)+"_SHORT", set.shorts[i]->t, set.shorts[i]->modified);
if(!success) {
break;
}
}
if(set.loads.count(i)) {
success &= createCoefficient(set.name, "P"+QString::number(i)+"_LOAD", set.loads[i]->t, set.loads[i]->modified);
if(!success) {
break;
}
}
for(int j=i+1;j<=numPorts;j++) {
auto c = set.getThrough(i,j);
if(c) {
success &= createCoefficient(set.name, "P"+QString::number(i)+QString::number(j)+"_THROUGH", c->t, c->modified);
if(!success) {
break;
}
}
}
if(!success) {
break;
}
}
}
// prune empty coefficient sets
@ -600,6 +729,194 @@ bool CalDevice::hasModifiedCoefficients()
return false;
}
void CalDevice::factoryUpdateDialog()
{
auto d = new QDialog();
auto ui = new Ui::FactoryUpdateDialog();
ui->setupUi(d);
ui->progress->setValue(0);
connect(this, &CalDevice::updateCoefficientsPercent, ui->progress, &QProgressBar::setValue, Qt::QueuedConnection);
auto updateEnableState = [=]() {
ui->updateFile->setEnabled(true);
ui->updateServer->setEnabled(true);
if(ui->updateServer->isChecked()) {
// can start
ui->startUpdate->setEnabled(true);
// file selection disabled
ui->file->setEnabled(false);
ui->browse->setEnabled(false);
} else {
// file selection enabled
ui->file->setEnabled(true);
ui->browse->setEnabled(true);
// start possible if some file is selected
ui->startUpdate->setEnabled(!ui->file->text().isEmpty());
}
};
connect(ui->updateServer, &QRadioButton::toggled, this, updateEnableState);
connect(ui->file, &QLineEdit::textChanged, this, updateEnableState);
connect(ui->browse, &QPushButton::clicked, this, [=](){
auto filename = QFileDialog::getOpenFileName(nullptr, "Select factory coefficient file", "", "Zip files (*.zip)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.isEmpty()) {
// aborted selection
return;
} else {
ui->file->setText(filename);
updateEnableState();
}
});
auto addStatus = [=](QString status) {
ui->msg->appendPlainText(status);
};
auto abortWithError = [=](QString error) {
ui->progress->setValue(0);
QTextCharFormat tf;
tf = ui->msg->currentCharFormat();
tf.setForeground(QBrush(Qt::red));
ui->msg->setCurrentCharFormat(tf);
ui->msg->appendPlainText(error);
tf.setForeground(QBrush(Qt::black));
ui->msg->setCurrentCharFormat(tf);
updateEnableState();
};
connect(this, &CalDevice::updateCoefficientsDone, this, [=](bool success) {
// transfer complete
// pass on communication failures again
connect(usb, &USBDevice::communicationFailure, this, &CalDevice::disconnected);
if(success) {
ui->progress->setValue(100);
addStatus("...done");
updateEnableState();
} else {
abortWithError("Transferring coefficients to LibreCAL failed");
}
});
auto updateFromFile = [=](QString filename) {
addStatus("Unzipping file...");
if(!QMicroz::extract(filename, ".")) {
abortWithError("Extracting zip file failed");
return;
}
// create coefficient set
auto set = CoefficientSet();
set.name = "FACTORY";
set.ports = 4;
set.createEmptyCoefficients();
struct Coeff {
QString description;
QString filename;
CoefficientSet::Coefficient *coeff;
};
std::array<Coeff, 18> coeffs = {{
{.description = "Port 1 open", .filename = "P1_OPEN.s1p", .coeff = set.opens[1]},
{.description = "Port 1 short", .filename = "P1_SHORT.s1p", .coeff = set.shorts[1]},
{.description = "Port 1 load", .filename = "P1_LOAD.s1p", .coeff = set.loads[1]},
{.description = "Port 2 open", .filename = "P2_OPEN.s1p", .coeff = set.opens[2]},
{.description = "Port 2 short", .filename = "P2_SHORT.s1p", .coeff = set.shorts[2]},
{.description = "Port 2 load", .filename = "P2_LOAD.s1p", .coeff = set.loads[2]},
{.description = "Port 3 open", .filename = "P3_OPEN.s1p", .coeff = set.opens[3]},
{.description = "Port 3 short", .filename = "P3_SHORT.s1p", .coeff = set.shorts[3]},
{.description = "Port 3 load", .filename = "P3_LOAD.s1p", .coeff = set.loads[3]},
{.description = "Port 4 open", .filename = "P4_OPEN.s1p", .coeff = set.opens[4]},
{.description = "Port 4 short", .filename = "P4_SHORT.s1p", .coeff = set.shorts[4]},
{.description = "Port 4 load", .filename = "P4_LOAD.s1p", .coeff = set.loads[4]},
{.description = "Port 1 to 2 through", .filename = "P12_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(1,2)]},
{.description = "Port 1 to 3 through", .filename = "P13_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(1,3)]},
{.description = "Port 1 to 4 through", .filename = "P14_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(1,4)]},
{.description = "Port 2 to 3 through", .filename = "P23_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(2,3)]},
{.description = "Port 2 to 4 through", .filename = "P24_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(2,4)]},
{.description = "Port 3 to 4 through", .filename = "P34_THROUGH.s2p", .coeff = set.throughs[set.portsToThroughIndex(3,4)]},
}};
for(const Coeff &c : coeffs) {
addStatus("Loading coefficient ("+c.description+")...");
try {
auto t = Touchstone::fromFile(c.filename.toStdString());
c.coeff->t = t;
c.coeff->modified = true;
QFile rmfile(c.filename);
rmfile.remove();
} catch (const std::exception &e) {
abortWithError("Failed: "+QString::fromStdString(e.what()));
return;
}
}
// delete previous factory set if available
for(unsigned int i=0;i<coeffSets.size();i++) {
if(coeffSets[i].name == "FACTORY") {
coeffSets.erase(coeffSets.begin() + i);
}
}
// add set to device
coeffSets.push_back(set);
// enable factory writing on device
addStatus("Enable factory coefficient writes...");
bool success = usb->Cmd(":FACT:ENABLEWRITE I_AM_SURE");
// delete all factory coefficients (this formats the factory partition)
success &= usb->Cmd(":FACT:DEL", 5000);
if(success) {
// start the transfer
addStatus("Transferring new coefficients to LibreCAL...");
// potential communication failures should not be passed on during this
disconnect(usb, &USBDevice::communicationFailure, this, &CalDevice::disconnected);
saveCoefficientSets();
} else {
abortWithError("Failed to erase previous factory calibration");
}
};
auto netw = new QNetworkAccessManager();
connect(netw, &QNetworkAccessManager::finished, this, [=](QNetworkReply *reply) {
if(reply->error() == QNetworkReply::NoError) {
// success
addStatus("Factory coefficients downloaded...");
QFile file(serial()+".zip");
if(!file.open(QIODevice::WriteOnly)) {
abortWithError("Failed to create folder for zipped data");
return;
}
file.write(reply->readAll());
file.flush();
file.close();
updateFromFile(serial()+".zip");
// remove zip file
file.remove();
} else {
abortWithError("No factory coefficients found. Check internet access and serial number");
}
});
connect(ui->startUpdate, &QPushButton::clicked, this, [=](){
ui->msg->clear();
ui->startUpdate->setEnabled(false);
ui->updateFile->setEnabled(false);
ui->updateServer->setEnabled(false);
ui->file->setEnabled(false);
ui->browse->setEnabled(false);
if(ui->updateServer->isChecked()) {
addStatus("Looking up factory coefficient data...");
QUrl url("https://librecal.kaeberich.com/calibrationdata/"+serial()+".zip");
netw->get(QNetworkRequest(url));
} else {
// update from file
updateFromFile(ui->file->text());
}
});
updateEnableState();
d->exec();
}
CalDevice::CoefficientSet::Coefficient *CalDevice::CoefficientSet::getOpen(int port)
{
if(opens.count(port)) {

View file

@ -96,6 +96,11 @@ public:
bool hasModifiedCoefficients();
bool coefficientTransferActive() { return transferActive; }
public slots:
void factoryUpdateDialog();
signals:
void updateCoefficientsPercent(int percent);
// emitted when all coefficients have been received and it is safe to call all functions again
@ -113,6 +118,7 @@ private:
int numPorts;
std::thread *loadThread;
bool abortLoading;
bool transferActive;
float firmware_major_minor;

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FactoryUpdateDialog</class>
<widget class="QDialog" name="FactoryUpdateDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>635</width>
<height>448</height>
</rect>
</property>
<property name="windowTitle">
<string>Factory Coefficient Update</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Updating the factory coefficients is normally not necessary. But in case they have been lost or modified accidentally, they can be set to the original state again.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="updateServer">
<property name="text">
<string>Update coefficients from manufacturing server</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="updateFile">
<property name="text">
<string>Update from file</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QLineEdit" name="file"/>
</item>
<item>
<widget class="QPushButton" name="browse">
<property name="toolTip">
<string>Select file</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="msg">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="startUpdate">
<property name="text">
<string>Update Factory Coefficients</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>

View file

@ -72,6 +72,10 @@ USBDevice::USBDevice(QString serial)
m_receiveThread = new std::thread(&USBDevice::USBHandleThread, this);
usbBuffer = new USBInBuffer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, 65536);
connect(usbBuffer, &USBInBuffer::DataReceived, this, &USBDevice::ReceivedData, Qt::DirectConnection);
// clear any possible queued data in USB device
QString dummy;
while(receive(&dummy, 10));
}
USBDevice::~USBDevice()
@ -85,11 +89,11 @@ USBDevice::~USBDevice()
delete m_receiveThread;
}
bool USBDevice::Cmd(QString cmd)
bool USBDevice::Cmd(QString cmd, unsigned int timeout)
{
QString rcv;
flushReceived();
bool success = send(cmd) && receive(&rcv);
bool success = send(cmd) && receive(&rcv, timeout);
if(success && rcv == "") {
// empty response expected by commad
return true;
@ -100,12 +104,12 @@ bool USBDevice::Cmd(QString cmd)
}
}
QString USBDevice::Query(QString query)
QString USBDevice::Query(QString query, unsigned int timeout)
{
flushReceived();
if(send(query)) {
QString rcv;
if(receive(&rcv)) {
if(receive(&rcv, timeout)) {
return rcv;
} else {
emit communicationFailure();
@ -212,6 +216,8 @@ void USBDevice::SearchDevices(std::function<bool (libusb_device_handle *, QStrin
bool USBDevice::send(const QString &s)
{
lineBuffer.clear();
qDebug() << "USB send:" << s;
unsigned char data[s.size()+2];
memcpy(data, s.toLatin1().data(), s.size());
memcpy(&data[s.size()], "\r\n", 2);
@ -237,6 +243,7 @@ bool USBDevice::receive(QString *s, unsigned int timeout)
}
}
*s = lineBuffer.takeFirst();
qDebug() << "USB recv:" << *s;
return true;
}

View file

@ -3,6 +3,13 @@
#include "Util/usbinbuffer.h"
#include <QtGlobal>
#ifdef Q_OS_MACOS
#include <libusb.h>
#else
#include <libusb-1.0/libusb.h>
#endif
#include <QString>
#include <set>
#include <functional>
@ -19,8 +26,8 @@ public:
USBDevice(QString serial = QString());
~USBDevice();
bool Cmd(QString cmd);
QString Query(QString query);
bool Cmd(QString cmd, unsigned int timeout = 2000);
QString Query(QString query, unsigned int timeout = 2000);
QString serial() const;
// Returns serial numbers of all connected devices

View file

@ -130,6 +130,9 @@ HEADERS += \
Traces/waterfallaxisdialog.h \
Traces/xyplotaxisdialog.h \
Traces/tracepolarchart.h \
Util/QMicroz/miniz.h \
Util/QMicroz/qmicroz.h \
Util/QMicroz/tools.h \
Util/prbs.h \
Util/qpointervariant.h \
Util/usbinbuffer.h \
@ -280,6 +283,9 @@ SOURCES += \
Traces/tracepolar.cpp \
Traces/waterfallaxisdialog.cpp \
Traces/xyplotaxisdialog.cpp \
Util/QMicroz/miniz.c \
Util/QMicroz/qmicroz.cpp \
Util/QMicroz/tools.cpp \
Util/prbs.cpp \
Util/usbinbuffer.cpp \
Util/util.cpp \
@ -312,6 +318,7 @@ SOURCES += \
LIBS += -lusb-1.0
unix:LIBS += -L/usr/lib/
win32:LIBS += -L"$$_PRO_FILE_PWD_" # Github actions placed libusb here
win32:DEFINES += QMICROZ_LIBRARY
osx:INCPATH += /usr/local/include
osx:LIBS += -L/usr/local/lib $(shell pkg-config --libs libusb-1.0)
@ -330,6 +337,7 @@ FORMS += \
Calibration/CalStandardReflectEditDialog.ui \
Calibration/CalStandardShortEditDialog.ui \
Calibration/CalStandardThroughEditDialog.ui \
Calibration/LibreCAL/factoryUpdateDialog.ui \
Calibration/LibreCAL/librecaldialog.ui \
Calibration/calibrationdialogui.ui \
Calibration/calkitdialog.ui \

View file

@ -0,0 +1,22 @@
Copyright 2013-2014 RAD Game Tools and Valve Software
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,550 @@
/*
* This file is part of QMicroz,
* under the MIT License.
* https://github.com/artemvlas/qmicroz
*
* Copyright (c) 2024 Artem Vlasenko
*/
#include "qmicroz.h"
#include "tools.h"
#include <QDir>
#include <QDebug>
const QString QMicroz::s_zip_ext = QStringLiteral(u".zip");
QMicroz::QMicroz() {}
QMicroz::QMicroz(const char* zip_path)
{
setZipFile(QString(zip_path));
}
QMicroz::QMicroz(const QString &zip_path)
{
setZipFile(zip_path);
}
QMicroz::QMicroz(const QByteArray &buffered_zip)
{
setZipBuffer(buffered_zip);
}
QMicroz::~QMicroz()
{
closeArchive();
}
bool QMicroz::setZipFile(const QString &zip_path)
{
if (QFileInfo(zip_path).isFile()) {
// try to open zip archive
mz_zip_archive *_za = tools::za_new(zip_path, tools::ZaReader);
if (_za) {
// close the currently opened one if any
closeArchive();
m_archive = _za;
m_zip_path = zip_path;
updateZipContents();
setOutputFolder(); // zip file's parent folder
return true;
}
}
qWarning() << "Wrong path:" << zip_path;
return false;
}
bool QMicroz::setZipBuffer(const QByteArray &buffered_zip)
{
// open zip archive
mz_zip_archive *_za = new mz_zip_archive();
if (mz_zip_reader_init_mem(_za, buffered_zip.constData(), buffered_zip.size(), 0)) {
// close the currently opened one if any
closeArchive();
// set the new one
m_archive = _za;
updateZipContents();
return true;
}
qWarning() << "Failed to open buffered zip";
delete _za;
return false;
}
void QMicroz::setOutputFolder(const QString &output_folder)
{
if (output_folder.isEmpty() && !m_zip_path.isEmpty()) {
// set zip file's parent folder
m_output_folder = QFileInfo(m_zip_path).absolutePath();
return;
}
m_output_folder = output_folder;
}
const QString& QMicroz::outputFolder() const
{
if (m_output_folder.isEmpty())
qWarning() << "No output folder setted";
return m_output_folder;
}
void QMicroz::closeArchive()
{
if (m_archive) {
tools::za_close(static_cast<mz_zip_archive *>(m_archive));
m_archive = nullptr;
m_zip_contents.clear();
m_zip_path.clear();
m_output_folder.clear();
}
}
const ZipContents& QMicroz::updateZipContents()
{
m_zip_contents.clear();
if (!m_archive)
return m_zip_contents;
mz_zip_archive *_za = static_cast<mz_zip_archive *>(m_archive);
// iterating...
for (int it = 0; it < count(); ++it) {
const QString _filename = tools::za_item_name(_za, it);
if (_filename.isEmpty()) {
break;
}
m_zip_contents[it] = _filename;
}
return m_zip_contents;
}
qint64 QMicroz::sizeUncompressed() const
{
qint64 _total_size = 0;
for (int it = 0; it < count(); ++it) {
_total_size += sizeUncompressed(it);
}
return _total_size;
}
const QString& QMicroz::zipFilePath() const
{
return m_zip_path;
}
const ZipContents& QMicroz::contents() const
{
return m_zip_contents;
}
int QMicroz::count() const
{
return mz_zip_reader_get_num_files(static_cast<mz_zip_archive *>(m_archive));
}
bool QMicroz::isFolder(int index) const
{
return name(index).endsWith(tools::s_sep); // '/'
}
bool QMicroz::isFile(int index) const
{
const QString _name = name(index);
return !_name.isEmpty() && !_name.endsWith(tools::s_sep);
}
QString QMicroz::name(int index) const
{
return contents().value(index);
}
qint64 QMicroz::sizeCompressed(int index) const
{
if (!m_archive)
return 0;
return tools::za_file_stat(static_cast<mz_zip_archive *>(m_archive), index).m_comp_size;
}
qint64 QMicroz::sizeUncompressed(int index) const
{
if (!m_archive)
return 0;
return tools::za_file_stat(static_cast<mz_zip_archive *>(m_archive), index).m_uncomp_size;
}
QDateTime QMicroz::lastModified(int index) const
{
if (!m_archive)
return QDateTime();
const qint64 _sec = tools::za_file_stat(static_cast<mz_zip_archive *>(m_archive), index).m_time;
return _sec > 0 ? QDateTime::fromSecsSinceEpoch(_sec) : QDateTime();
}
bool QMicroz::extractAll()
{
if (!m_archive) {
qDebug() << "No zip archive setted";
return false;
}
return tools::extract_all_to_disk(static_cast<mz_zip_archive*>(m_archive), outputFolder());
}
// !recreate_path >> place in the root of the output folder
bool QMicroz::extractIndex(int index, bool recreate_path)
{
if (!m_archive) {
qDebug() << "No zip archive setted";
return false;
}
if (index == -1 || outputFolder().isEmpty())
return false;
qDebug() << "Extract:" << index << "from:" << m_zip_path;
qDebug() << "Output folder:" << outputFolder();
// create output folder if it doesn't exist
if (!tools::createFolder(outputFolder())) {
return false;
}
// extracting...
// the name is also a relative path inside the archive
const QString _filename = name(index);
if (_filename.isEmpty()) {
return false;
}
qDebug() << "Extracting:" << _filename;
const QString _outpath = tools::joinPath(outputFolder(),
recreate_path ? _filename : QFileInfo(_filename).fileName());
// create a new path on disk
const QString _parent_folder = QFileInfo(_outpath).absolutePath();
if (!tools::createFolder(_parent_folder)) {
return false;
}
// subfolder, no data to extract
if (_filename.endsWith(tools::s_sep)) {
qDebug() << "Subfolder extracted";
}
// extract file
else if (!tools::extract_to_file(static_cast<mz_zip_archive *>(m_archive), index, _outpath)) {
return false;
}
qDebug() << "Unzip complete.";
return true;
}
bool QMicroz::extractFile(const QString &file_name, bool recreate_path)
{
return extractIndex(findIndex(file_name), recreate_path);
}
BufList QMicroz::extractToBuf() const
{
BufList _res;
if (!m_archive) {
qWarning() << "No archive setted";
return _res;
}
qDebug() << "Extracting to RAM:" << (m_zip_path.isEmpty() ? "buffered zip" : m_zip_path);
// extracting...
for (int it = 0; it < count(); ++it) {
const QString _filename = name(it);
if (_filename.isEmpty()) {
break;
}
// subfolder, no data to extract
if (_filename.endsWith(tools::s_sep))
continue;
qDebug() << "Extracting:" << (it + 1) << '/' << count() << _filename;
// extract file
const QByteArray _data = extractData(it);
if (!_data.isNull())
_res[_filename] = _data;
}
qDebug() << "Unzipped:" << _res.size() << "files";
return _res;
}
BufFile QMicroz::extractToBuf(int index) const
{
BufFile _res;
if (!m_archive) {
qWarning() << "No archive setted";
return _res;
}
if (index == -1)
return _res;
qDebug() << "Extracting to RAM:" << (m_zip_path.isEmpty() ? "buffered zip" : m_zip_path);
const QString _filename = name(index);
if (_filename.isEmpty())
return _res;
// subfolder, no data to extract
if (_filename.endsWith(tools::s_sep)) {
qDebug() << "Subfolder, no data to extract:" << _filename;
return _res;
}
qDebug() << "Extracting:" << _filename;
// extract file
const QByteArray _data = extractData(index);
if (!_data.isNull()) {
_res.m_name = _filename;
_res.m_data = _data;
_res.m_modified = lastModified(index);
qDebug() << "Unzipped:" << _data.size() << "bytes";
}
return _res;
}
BufFile QMicroz::extractFileToBuf(const QString &file_name) const
{
return extractToBuf(findIndex(file_name));
}
// Recommended in most cases if speed and memory requirements are not critical.
QByteArray QMicroz::extractData(int index) const
{
return tools::extract_to_buffer(static_cast<mz_zip_archive *>(m_archive), index);
}
// This function is faster and consumes less resources than the previous one,
// but requires an additional delete operation to avoid memory leaks. ( delete _array.constData(); )
QByteArray QMicroz::extractDataRef(int index) const
{
return tools::extract_to_buffer(static_cast<mz_zip_archive *>(m_archive),
index, false);
}
// STATIC functions ---->>>
bool QMicroz::extract(const QString &zip_path)
{
// extract to parent folder
return extract(zip_path, QFileInfo(zip_path).absolutePath());
}
bool QMicroz::extract(const QString &zip_path, const QString &output_folder)
{
//qDebug() << "Extract:" << zip_path;
//qDebug() << "Output folder:" << output_folder;
// open zip archive
mz_zip_archive *__za = tools::za_new(zip_path, tools::ZaReader);
if (!__za) {
return false;
}
// extracting...
bool is_success = tools::extract_all_to_disk(__za, output_folder);
// finish
tools::za_close(__za);
return is_success;
}
bool QMicroz::compress_here(const QString &path)
{
QFileInfo __fi(path);
if (__fi.isFile()) {
return compress_file(path);
}
else if (__fi.isDir()) {
return compress_folder(path);
}
else {
qDebug() << "QMicroz::compress | WRONG path:" << path;
return false;
}
}
bool QMicroz::compress_here(const QStringList &paths)
{
if (paths.isEmpty())
return false;
const QString _rootfolder = QFileInfo(paths.first()).absolutePath();
const QString _zipname = QFileInfo(_rootfolder).fileName() + s_zip_ext;
const QString _zippath = tools::joinPath(_rootfolder, _zipname);
return compress_list(paths, _zippath);
}
bool QMicroz::compress_file(const QString &source_path)
{
QFileInfo __fi(source_path);
const QString _zip_name = __fi.completeBaseName() + s_zip_ext;
const QString _out_path = tools::joinPath(__fi.absolutePath(), _zip_name);
return compress_file(source_path, _out_path);
}
bool QMicroz::compress_file(const QString &source_path, const QString &zip_path)
{
if (!QFileInfo::exists(source_path)) {
qWarning() << "File not found:" << source_path;
return false;
}
return compress_list({ source_path }, zip_path);
}
bool QMicroz::compress_folder(const QString &source_path)
{
QFileInfo __fi(source_path);
const QString _file_name = __fi.fileName() + s_zip_ext;
const QString _parent_folder = __fi.absolutePath();
const QString _out_path = tools::joinPath(_parent_folder, _file_name);
return compress_folder(source_path, _out_path);
}
bool QMicroz::compress_folder(const QString &source_path, const QString &zip_path)
{
if (!QFileInfo::exists(source_path)) {
qWarning() << "Folder not found:" << source_path;
return false;
}
return compress_list({ source_path }, zip_path);
}
bool QMicroz::compress_list(const QStringList &paths, const QString &zip_path)
{
if (paths.isEmpty())
return false;
const QString _root = QFileInfo(paths.first()).absolutePath();
const QString _info = QStringLiteral(u"Zipping: ")
+ (paths.size() == 1 ? paths.first() : (QString::number(paths.size()) + QStringLiteral(u" items")));
qDebug() << _info;
qDebug() << "Output:" << zip_path;
// check if all paths are in the same root
if (paths.size() > 1) {
for (const QString &_path : paths) {
if (_root != QFileInfo(_path).absolutePath()) {
qWarning() << "ERROR: all items must be in the same folder!";
return false;
}
}
}
QStringList _worklist;
// parsing the path list
for (const QString &_path : paths) {
QFileInfo __fi(_path);
if (!__fi.exists() || __fi.isSymLink()) {
qWarning() << "Skipped:" << _path;
continue;
}
if (__fi.isFile())
_worklist << _path;
else
_worklist << tools::folderContent(_path);
}
return tools::createArchive(zip_path, _worklist, _root);
}
bool QMicroz::compress_buf(const BufList &buf_data, const QString &zip_path)
{
qDebug() << "Zipping buffered data to:" << zip_path;
if (buf_data.isEmpty()) {
qDebug() << "No input data. Nothing to zip.";
return false;
}
// create and open the output zip file
mz_zip_archive *__za = tools::za_new(zip_path, tools::ZaWriter);
if (!__za) {
return false;
}
// process
BufList::const_iterator it;
for (it = buf_data.constBegin(); it != buf_data.constEnd(); ++it) {
if (!tools::add_item_data(__za, it.key(), it.value())) {
tools::za_close(__za);
return false;
}
}
// cleanup
mz_zip_writer_finalize_archive(__za);
tools::za_close(__za);
qDebug() << "Done";
return true;
}
bool QMicroz::compress_buf(const QByteArray &data, const QString &file_name, const QString &zip_path)
{
BufList _buf;
_buf[file_name] = data;
return compress_buf(_buf, zip_path);
}
int QMicroz::findIndex(const QString &file_name) const
{
ZipContents::const_iterator it;
// full path matching
for (it = m_zip_contents.constBegin(); it != m_zip_contents.constEnd(); ++it) {
if (file_name == it.value())
return it.key();
}
// deep search, matching only the name
if (!file_name.contains(tools::s_sep)) {
for (it = m_zip_contents.constBegin(); it != m_zip_contents.constEnd(); ++it) {
if (!it.value().endsWith('/') // if not a subfolder
&& file_name == QFileInfo(it.value()).fileName())
{
return it.key();
}
}
}
qDebug() << "Index not found:" << file_name;
return -1;
}

View file

@ -0,0 +1,115 @@
/*
* This file is part of QMicroz,
* under the MIT License.
* https://github.com/artemvlas/qmicroz
*
* Copyright (c) 2024 Artem Vlasenko
*/
#ifndef QMICROZ_H
#define QMICROZ_H
#include <QtCore/qglobal.h>
#if defined(QMICROZ_LIBRARY)
#define QMICROZ_EXPORT Q_DECL_EXPORT
#else
#define QMICROZ_EXPORT Q_DECL_IMPORT
#endif
#include <QStringList>
#include <QMap>
#include <QDateTime>
// { "path inside zip" : data }
using BufList = QMap<QString, QByteArray>;
struct BufFile {
BufFile() {}
BufFile(const QString &filename, const QByteArray &data)
: m_name(filename), m_data(data) {}
explicit operator bool() const { return !m_name.isEmpty(); }
QString m_name; // file name (path inside the archive)
QByteArray m_data; // file data (uncompressed)
QDateTime m_modified; // date and time of the file's last modification
}; // struct BufFile
// list of files {index : path} contained in the archive
using ZipContents = QMap<int, QString>;
class QMICROZ_EXPORT QMicroz
{
public:
QMicroz();
explicit QMicroz(const char *zip_path); // to avoid ambiguity
explicit QMicroz(const QString &zip_path); // path to existing zip file
explicit QMicroz(const QByteArray &buffered_zip); // existing zip archive buffered in memory
~QMicroz();
explicit operator bool() const { return (bool)m_archive; } // checks whether the archive is setted
bool setZipFile(const QString &zip_path); // sets and opens the zip for the current object
bool setZipBuffer(const QByteArray &buffered_zip); // sets a buffered in memory zip archive
void setOutputFolder(const QString &output_folder = QString()); // path to the folder where to place the extracted files; empty --> parent dir
void closeArchive(); // closes the currently setted zip and clears the member values
// Info about the Archive
const QString& zipFilePath() const; // returns the path to the current zip-file ("m_zip_path")
const QString& outputFolder() const; // the path to place the extracted files
qint64 sizeUncompressed() const; // total uncompressed data size (space required for extraction)
// Zipped Items Info
const ZipContents& contents() const; // returns a list of files {index : path} contained in the archive
int count() const; // returns the number of items in the archive
int findIndex(const QString &file_name) const; // returns the index by the specified file name, -1 if not found
bool isFolder(int index) const; // whether the specified index belongs to the folder
bool isFile(int index) const; // ... to the file
QString name(int index) const; // returns the name/path corresponding to the index
qint64 sizeCompressed(int index) const; // returns the compressed size of the file at the specified index
qint64 sizeUncompressed(int index) const; // the uncompressed size
QDateTime lastModified(int index) const; // returns the file modification date stored in the archive
// Extraction
bool extractAll(); // extracts the archive into the output folder (or the parent one)
bool extractIndex(int index, bool recreate_path = true); // extracts the file with index to disk
bool extractFile(const QString &file_name, bool recreate_path = true); // finds the file_name and extracts if any; slower than 'extractIndex'
BufList extractToBuf() const; // unzips all files into the RAM buffer { path : data }
BufFile extractToBuf(int index) const; // extracts the selected index only
BufFile extractFileToBuf(const QString &file_name) const; // finds the file_name and extracts to Buf; slower than (index)
QByteArray extractData(int index) const; // returns the extracted file data; QByteArray owns the copied data
QByteArray extractDataRef(int index) const; // QByteArray does NOT own the data! To free memory: delete _array.constData();
// STATIC functions
static bool extract(const QString &zip_path); // extracting the zip into the parent dir
static bool extract(const QString &zip_path, const QString &output_folder); // to output_folder
static bool compress_here(const QString &path); // zips a file or folder (path), >> parent dir
static bool compress_here(const QStringList &paths); // paths to files or/and folders
static bool compress_file(const QString &source_path); // zips a file, >> parent dir
static bool compress_file(const QString &source_path, const QString &zip_path); // >> zip_path
static bool compress_folder(const QString &source_path); // zips a folder, >> parent dir
static bool compress_folder(const QString &source_path, const QString &zip_path); // >> zip_path
static bool compress_list(const QStringList &paths, const QString &zip_path); // zips a list of files or folders (paths), >> zip_path
static bool compress_buf(const BufList &buf_data, const QString &zip_path); // creates an archive with files from the listed paths and data
static bool compress_buf(const QByteArray &data,
const QString &file_name, const QString &zip_path); // creates an archive (zip_path) containing a file (file_name, data)
private:
const ZipContents& updateZipContents(); // updates the list of current archive contents
// the void pointer is used to allow the miniz header not to be included
void *m_archive = nullptr;
QString m_zip_path; // path to the current zip file
QString m_output_folder; // folder to place the extracted files
ZipContents m_zip_contents; // holds the list of current contents { index : filename (or path) }
static const QString s_zip_ext; // ".zip"
}; // class QMicroz
#endif // QMICROZ_H

View file

@ -0,0 +1,274 @@
/*
* This file is part of QMicroz,
* under the MIT License.
* https://github.com/artemvlas/qmicroz
*
* Copyright (c) 2024 Artem Vlasenko
*/
#include "tools.h"
#include <QDebug>
#include <QDirIterator>
#include <QStringBuilder>
namespace tools {
mz_zip_archive* za_new(const QString &zip_path, ZaType za_type)
{
// open zip archive
mz_zip_archive *_za = new mz_zip_archive();
// TODO: make a merge (for example, by pointer)
bool result = za_type ? mz_zip_writer_init_file(_za, zip_path.toUtf8().constData(), 0)
: mz_zip_reader_init_file(_za, zip_path.toUtf8().constData(), 0);
if (!result) {
qWarning() << "Failed to open zip file:" << zip_path;
delete _za;
return nullptr;
}
return _za;
}
mz_zip_archive_file_stat za_file_stat(mz_zip_archive* pZip, int file_index)
{
mz_zip_archive_file_stat file_stat;
if (mz_zip_reader_file_stat(pZip, file_index, &file_stat)) {
return file_stat;
}
qWarning() << "Failed to get file info:" << file_index;
return mz_zip_archive_file_stat();
}
bool za_close(mz_zip_archive* pZip)
{
if (pZip && mz_zip_end(pZip)) {
delete pZip;
qDebug() << "Archive closed";
return true;
}
qWarning() << "Failed to close archive";
return false;
}
QString za_item_name(mz_zip_archive* pZip, int file_index)
{
return za_file_stat(pZip, file_index).m_filename;
}
bool createArchive(const QString &zip_path, const QStringList &item_paths, const QString &zip_root)
{
if (item_paths.isEmpty()) {
qDebug() << "No input paths. Nothing to zip.";
return false;
}
// create and open the output zip file
mz_zip_archive *_za = za_new(zip_path, ZaWriter);
if (!_za) {
return false;
}
// process
const bool _res = add_item_list(_za, item_paths, zip_root);
if (_res) {
mz_zip_writer_finalize_archive(_za);
qDebug() << "Done";
}
// cleanup
za_close(_za);
return _res;
}
bool add_item_data(mz_zip_archive *p_zip, const QString &_item_path, const QByteArray &_data)
{
qDebug() << "Adding:" << _item_path;
if (!mz_zip_writer_add_mem(p_zip,
_item_path.toUtf8().constData(),
_data.constData(),
_data.size(),
compressLevel(_data.size())))
{
qWarning() << "Failed to compress file:" << _item_path;
return false;
}
return true;
}
bool add_item_folder(mz_zip_archive *p_zip, const QString &in_path)
{
return add_item_data(p_zip,
in_path.endsWith(s_sep) ? in_path : in_path + s_sep,
QByteArray());
}
bool add_item_file(mz_zip_archive *p_zip, const QString &fs_path, const QString &in_path)
{
qDebug() << "Adding:" << in_path;
return mz_zip_writer_add_file(p_zip, // zip archive
in_path.toUtf8().constData(), // path inside the zip
fs_path.toUtf8().constData(), // filesystem path
NULL, 0,
compressLevel(QFileInfo(fs_path).size()));
}
bool add_item_list(mz_zip_archive *p_zip, const QStringList &items, const QString &rootFolder)
{
QDir __d(rootFolder);
// parsing a list of paths
for (const QString &_item : items) {
QFileInfo __fi(_item);
const QString _relPath = __d.relativeFilePath(_item);
// adding item
if (__fi.isFile() && !add_item_file(p_zip, _item, _relPath) // file
|| (__fi.isDir() && !add_item_folder(p_zip, _relPath))) // subfolder
{
// adding failed
return false;
}
}
return true;
}
bool extract_to_file(mz_zip_archive* pZip, int file_index, const QString &outpath)
{
if (mz_zip_reader_extract_to_file(pZip,
file_index,
outpath.toUtf8().constData(),
0))
{
return true;
}
qWarning() << "Failed to extract file:" << file_index;
return false;
}
QByteArray extract_to_buffer(mz_zip_archive* pZip, int file_index, bool copy_data)
{
size_t __size = 0;
char *_c = (char*)mz_zip_reader_extract_to_heap(pZip, file_index, &__size, 0);
if (_c) {
if (copy_data) {
// COPY data to QByteArray
QByteArray __b(_c, __size);
// clear extracted from heap
delete _c;
return __b;
}
// Reference to the data in the QByteArray.
// Data should be deleted on the caller side: delete _array.constData();
return QByteArray::fromRawData(_c, __size);
}
qWarning() << "Failed to extract file:" << file_index;
return QByteArray();
}
bool extract_all_to_disk(mz_zip_archive *pZip, const QString &output_folder)
{
if (output_folder.isEmpty()) {
qDebug() << "No output folder provided";
return false;
}
const int _num_items = mz_zip_reader_get_num_files(pZip);
if (_num_items == 0) {
qDebug() << "No files to extract";
return false;
}
qDebug() << "Extracting" << _num_items << "items to:" << output_folder;
// extracting...
bool is_success = true;
for (int it = 0; it < _num_items; ++it) {
const QString _filename = za_item_name(pZip, it);
qDebug() << "Extracting:" << (it + 1) << '/' << _num_items << _filename;
const QString _outpath = joinPath(output_folder, _filename);
// create new path on the disk
const QString _parent_folder = QFileInfo(_outpath).absolutePath();
if (!createFolder(_parent_folder)) {
is_success = false;
break;
}
// subfolder, no data to extract
if (_filename.endsWith(s_sep))
continue;
// extract file
if (!extract_to_file(pZip, it, _outpath)) {
is_success = false;
break;
}
}
qDebug() << (is_success ? "Unzip complete." : "Unzip failed.");
return is_success;
}
QStringList folderContent(const QString &folder, bool addRoot)
{
QStringList _items;
if (addRoot) // add root folder
_items << folder;
QDirIterator it(folder,
QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Hidden,
QDirIterator::Subdirectories);
while (it.hasNext()) {
_items << it.next();
}
return _items;
}
bool createFolder(const QString &path)
{
if (QFileInfo::exists(path)
|| QDir().mkpath(path))
{
return true;
}
qWarning() << "Failed to create directory:" << path;
return false;
}
bool endsWithSlash(const QString &path)
{
return (path.endsWith('/') || path.endsWith('\\'));
}
QString joinPath(const QString &abs_path, const QString &rel_path)
{
return endsWithSlash(abs_path) ? abs_path + rel_path
: abs_path % s_sep % rel_path;
}
mz_uint compressLevel(qint64 data_size)
{
return (data_size > 40) ? MZ_DEFAULT_COMPRESSION : MZ_NO_COMPRESSION;
}
}// namespace tools

View file

@ -0,0 +1,72 @@
/*
* This file is part of QMicroz,
* under the MIT License.
* https://github.com/artemvlas/qmicroz
*
* Copyright (c) 2024 Artem Vlasenko
*/
#ifndef TOOLS_H
#define TOOLS_H
#include <QString>
#include "miniz.h"
namespace tools {
static const QChar s_sep = u'/';
enum ZaType { ZaReader, ZaWriter };
// creates and initializes a new archive
mz_zip_archive* za_new(const QString &zip_path, ZaType za_type);
// returns info about the file contained in the archive
mz_zip_archive_file_stat za_file_stat(mz_zip_archive* pZip, int file_index);
// closes and deletes the archive
bool za_close(mz_zip_archive* pZip);
// returns the name (path) of a file or folder in the archive at the specified index
QString za_item_name(mz_zip_archive* pZip, int file_index);
// creates an archive at the specified path and adds a list of files and folders to it
// root is the part of the path relative to which paths in the archive will be created
bool createArchive(const QString &zip_path, const QStringList &item_paths, const QString &zip_root);
// adds to the archive an entry with file or folder data
bool add_item_data(mz_zip_archive *p_zip, const QString &_item_path, const QByteArray &_data);
// adds an empty subfolder item to the zip; 'in_path' is the path inside the archive
bool add_item_folder(mz_zip_archive *p_zip, const QString &in_path);
// adds file item and data to zip; 'fs_path' is the filesystem path; 'in_path' is the path inside the archive
bool add_item_file(mz_zip_archive *p_zip, const QString &fs_path, const QString &in_path);
// parses the list of file/folder paths and adds them to the archive
bool add_item_list(mz_zip_archive *p_zip, const QStringList &items, const QString &rootFolder);
// extracts a file with the specified index from the archive to disk at the specified path
bool extract_to_file(mz_zip_archive* pZip, int file_index, const QString &outpath);
// extracts file data at the specified index into a buffer
QByteArray extract_to_buffer(mz_zip_archive* pZip, int file_index, bool copy_data = true);
// extracts the entire contents of the archive into the specified folder
bool extract_all_to_disk(mz_zip_archive *pZip, const QString &output_folder);
// returns a list of folder content paths; addRoot: the root folder is added to the list
QStringList folderContent(const QString &folder, bool addRoot = true);
// creates a folder at the specified path
// if already exists or created successfully, returns true; otherwise false
bool createFolder(const QString &path);
// does the path string end with a slash
bool endsWithSlash(const QString &path);
// connects two parts of a path, checking for the presence of a separator
QString joinPath(const QString &abs_path, const QString &rel_path);
// returns "no compression" for micro files, for others by default
mz_uint compressLevel(qint64 data_size);
}
#endif // TOOLS_H

View file

@ -124,6 +124,9 @@ SOURCES += \
../LibreVNA-GUI/Traces/tracexyplot.cpp \
../LibreVNA-GUI/Traces/waterfallaxisdialog.cpp \
../LibreVNA-GUI/Traces/xyplotaxisdialog.cpp \
../LibreVNA-GUI/Util/QMicroz/miniz.c \
../LibreVNA-GUI/Util/QMicroz/qmicroz.cpp \
../LibreVNA-GUI/Util/QMicroz/tools.cpp \
../LibreVNA-GUI/Util/prbs.cpp \
../LibreVNA-GUI/Util/util.cpp \
../LibreVNA-GUI/Util/usbinbuffer.cpp \
@ -318,6 +321,9 @@ HEADERS += \
../LibreVNA-GUI/Traces/tracexyplot.h \
../LibreVNA-GUI/Traces/waterfallaxisdialog.h \
../LibreVNA-GUI/Traces/xyplotaxisdialog.h \
../LibreVNA-GUI/Util/QMicroz/miniz.h \
../LibreVNA-GUI/Util/QMicroz/qmicroz.h \
../LibreVNA-GUI/Util/QMicroz/tools.h \
../LibreVNA-GUI/Util/prbs.h \
../LibreVNA-GUI/Util/util.h \
../LibreVNA-GUI/Util/usbinbuffer.h \
@ -436,5 +442,6 @@ unix:LIBS += -L/usr/lib/
REVISION = $$system(git rev-parse HEAD)
DEFINES += GITHASH=\\"\"$$REVISION\\"\"
DEFINES += FW_MAJOR=1 FW_MINOR=5 FW_PATCH=0 FW_SUFFIX=""#\\"\"-alpha.2\\"\"
DEFINES += FW_MAJOR=1 FW_MINOR=6 FW_PATCH=1 FW_SUFFIX=""#\\"\"-alpha.2\\"\"
DEFINES -= _UNICODE UNICODE
win32:DEFINES += QMICROZ_LIBRARY