fabric/utils/misc.cpp
fabric/utils/misc.cpp
Namespaces
| Name |
|---|
| Syntalos |
Source code
/*
* Copyright (C) 2016-2026 Matthias Klumpp <matthias@tenstral.net>
*
* Licensed under the GNU Lesser General Public License Version 3
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the license, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
#include "misc.h"
#include "config.h"
#include "datactl/edlutils.h"
#include <filesystem>
#include <linux/magic.h>
#include <sys/vfs.h>
#include <blake3.h>
#include <QCoreApplication>
#include <QDir>
#include <QRandomGenerator>
#include <QStandardPaths>
#include <QThread>
#include <QTime>
#include <QProcessEnvironment>
namespace fs = std::filesystem;
namespace Syntalos
{
QString createRandomString(int len)
{
const auto possibleChars = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString str;
for (int i = 0; i < len; i++) {
int index = QRandomGenerator::global()->generate() % possibleChars.length();
QChar nextChar = possibleChars.at(index);
str.append(nextChar);
}
return str;
}
QString simplifyStrForModuleName(const QString &s)
{
const auto tmp = s.simplified().replace("/", "_").replace("\\", "_");
if (tmp.isEmpty())
return QStringLiteral("Unnamed");
return tmp;
}
QString simplifyStrForFileBasename(const QString &s, bool lowerCase, uint maxLen)
{
// Qt handles the Unicode-aware whitespace collapsing and lowercasing; makeCompactName()
// does the rest. When lowercased we dash-separate words (camelCase is lost once lowercased),
// otherwise we join them (camelCase stays readable).
auto base = s.simplified();
if (lowerCase)
base = base.toLower();
return QString::fromStdString(
edl::makeCompactName(
base.toStdString(),
{.maxLength = maxLen, .fallback = "unnamed", .wordSeparator = lowerCase ? '-' : '\0'}));
}
std::string simplifyStrForFileBasename(const std::string &s, bool lowerCase, uint maxLen)
{
auto base = QString::fromStdString(s).simplified();
if (lowerCase)
base = base.toLower();
return edl::makeCompactName(
base.toStdString(),
{.maxLength = maxLen, .fallback = "unnamed", .wordSeparator = lowerCase ? '-' : '\0'});
}
QStringList qStringSplitLimit(const QString &str, const QChar &sep, int maxSplit, Qt::CaseSensitivity cs)
{
QStringList list;
int start = 0;
int end;
while ((end = str.indexOf(sep, start, cs)) != -1) {
if (start != end)
list.append(str.mid(start, end - start));
start = end + 1;
if (maxSplit > 0) {
if (list.length() > maxSplit)
break;
}
}
if (start != str.size())
list.append(str.mid(start));
return list;
}
QString syntalosVersionFull()
{
auto syVersion = QStringLiteral(PROJECT_VERSION);
auto syVcs = QStringLiteral(SY_VCS_TAG).replace(syVersion, "");
if (syVcs.contains("-"))
syVcs = syVcs.section('-', 1);
if (syVcs.startsWith("v"))
syVcs.remove(0, 1);
if (syVcs == QStringLiteral("+")) {
syVersion = syVersion + QStringLiteral("+");
syVcs = "";
}
return syVcs.isEmpty() ? syVersion : QStringLiteral("%1 (%2)").arg(syVersion, syVcs);
}
QString syntalosVersion()
{
return QStringLiteral(PROJECT_VERSION);
}
bool isInFlatpakSandbox()
{
if (qEnvironmentVariable("container") == QStringLiteral("flatpak"))
return true;
// We check for FLATPAK_ID as well to make this function work for older versions
// of Flatpak. 1.14.4 or higher is confirmed to not need this check.
if (qEnvironmentVariable("FLATPAK_ID").startsWith("org.syntalos"))
return true;
return false;
}
QString findHostFile(const QString &path)
{
if (isInFlatpakSandbox()) {
const auto hostPath = fs::path(QStringLiteral("/run/host/%1").arg(path).toStdString()).lexically_normal();
if (fs::exists(hostPath))
return QString::fromStdString(hostPath.string());
} else {
if (fs::exists(fs::path(path.toStdString())))
return path;
}
return QString();
}
bool hostUdevRuleExists(const QString &ruleFilename)
{
QStringList udevPaths = {"/lib/udev/rules.d", "/usr/lib/udev/rules.d", "/etc/udev/rules.d"};
for (const auto &root : udevPaths) {
if (!findHostFile(root + "/" + ruleFilename).isEmpty())
return true;
}
return false;
}
QString appDataRootDir()
{
if (isInFlatpakSandbox())
return QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation))
.filePath(QStringLiteral(".var/app/org.syntalos.syntalos/data"));
// NOTE: We deliberately do not use AppDataLocation here, as that depends on the
// application name and helper binaries (e.g. the encode helper) must share this directory.
return QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation))
.filePath(QStringLiteral("Syntalos"));
}
QString tempDirRoot()
{
return QDir::tempPath();
}
static bool isFileOnTmpfs(const QString &fname)
{
struct statfs info;
statfs(qPrintable(fname), &info);
if (info.f_type == TMPFS_MAGIC)
return true;
return false;
}
QString tempDirLargeRoot()
{
QString tmpDir = QStringLiteral("/var/tmp");
if (fs::exists(tmpDir.toStdString()) && !isFileOnTmpfs(tmpDir))
return tmpDir;
tmpDir = tempDirRoot();
if (!isFileOnTmpfs(tmpDir))
return tmpDir;
return QStandardPaths::writableLocation(QStandardPaths::TempLocation);
}
void delay(int waitMsec)
{
if (waitMsec <= 54) {
// if it's just a short wait, we don't bother with the event loop
QThread::usleep(waitMsec * 1000);
return;
}
QTime doneTime = QTime::currentTime().addMSecs(waitMsec);
while (QTime::currentTime() < doneTime) {
QThread::usleep(500);
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
}
}
bool isBinaryInPath(const QString &binaryName)
{
QString path = QProcessEnvironment::systemEnvironment().value("PATH");
QStringList directories = path.split(QDir::listSeparator());
// check for the binary in all directories in PATH
for (const QString &dir : directories) {
QFileInfo fileInfo(QDir(dir), binaryName);
if (fileInfo.exists() && fileInfo.isExecutable()) {
return true;
}
}
return false;
}
QByteArray blake3HashForData(const QByteArray &data)
{
blake3_hasher hasher;
blake3_hasher_init(&hasher);
blake3_hasher_update(&hasher, data.constData(), data.size());
uint8_t out[BLAKE3_OUT_LEN];
blake3_hasher_finalize(&hasher, out, BLAKE3_OUT_LEN);
return {reinterpret_cast<const char *>(out), BLAKE3_OUT_LEN};
}
auto blake3HashForFile(const QString &filename) -> std::expected<QByteArray, QString>
{
QFile file(filename);
if (!file.open(QIODevice::ReadOnly))
return std::unexpected(
QStringLiteral("Unable to open file %1 for hashing: %2").arg(filename, file.errorString()));
const auto data = file.readAll();
blake3_hasher hasher;
blake3_hasher_init(&hasher);
blake3_hasher_update(&hasher, data.constData(), data.size());
uint8_t out[BLAKE3_OUT_LEN];
blake3_hasher_finalize(&hasher, out, BLAKE3_OUT_LEN);
return QByteArray(reinterpret_cast<const char *>(out), BLAKE3_OUT_LEN);
}
QString formatByteSize(qint64 bytes)
{
return QLocale().formattedDataSize(bytes, 1, QLocale::DataSizeSIFormat);
}
QString formatApproxDuration(double seconds)
{
if (seconds < 60)
return QStringLiteral("less than a minute");
if (seconds < 90 * 60)
return QStringLiteral("about %1 minutes").arg(qRound(seconds / 60.0));
if (seconds < 48 * 3600)
return QStringLiteral("about %1 hours").arg(seconds / 3600.0, 0, 'f', 1);
return QStringLiteral("about %1 days").arg(seconds / (24 * 3600.0), 0, 'f', 1);
}
} // namespace Syntalos
Updated on 2026-09-06 at 20:30:11 +0000