Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[53] Allow selection of Unraid OS Language within the USB creator #62

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/downloadextractthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,48 @@ void DownloadExtractThread::extractMultiFileRun()
}
}

if(_imgWriterSettings.contains("UNRAID_LANG_JSON")) {
// remove this tmp file now that we're definitely done with it
QFile langJson(_imgWriterSettings["UNRAID_LANG_JSON"].toByteArray());
langJson.remove();
}

if(_imgWriterSettings.contains("UNRAID_LANG_CODE"))
{
QString unraidLangCode(_imgWriterSettings["UNRAID_LANG_CODE"].toString());
if(_imgWriterSettings.contains("UNRAID_LANG_XML")) {
QFile langXml(_imgWriterSettings["UNRAID_LANG_XML"].toByteArray());
QFile::rename(langXml.fileName(), folder + "/config/plugins/lang-" + unraidLangCode + ".xml");
}

if(_imgWriterSettings.contains("UNRAID_LANG_ZIP")) {
QFile langZip(_imgWriterSettings["UNRAID_LANG_ZIP"].toByteArray());
QFile::rename(langZip.fileName(), folder + "/config/plugins/dynamix/lang-" + unraidLangCode + ".zip");
}
QFile dynamixCfg(folder + "/config/plugins/dynamix/dynamix.cfg");
if (dynamixCfg.exists())
{
dynamixCfg.open(QIODevice::ReadOnly);
QString dataText = dynamixCfg.readAll();
dynamixCfg.close();

QString oldData(dataText);
dataText.replace("locale=\"\"", "locale=\"" + unraidLangCode + "\"", Qt::CaseInsensitive);
if(oldData == dataText)
{
// if this string wasn't found for replacement, just add it
dataText += "locale=\"" + unraidLangCode + "\"";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure we put this locale= block under the "[display]" section - you may want to use an XML parser to parse the file and add a new section, I believe that you may want to parse this as XML

#include <QDomDocument>
#include <QString>
#include <QDebug>

void addLocaleToDisplaySection(QString& dataText, const QString& unraidLangCode) {
    QDomDocument doc;
    QString errorMsg;
    int errorLine, errorColumn;

    // Load the dataText as an XML document
    if (!doc.setContent(dataText, &errorMsg, &errorLine, &errorColumn)) {
        qDebug() << "Error parsing XML at line" << errorLine << "column" << errorColumn << ":" << errorMsg;
        return;
    }

    // Find the [display] section
    QDomElement root = doc.documentElement(); // Get the root element
    QDomNodeList displaySections = root.elementsByTagName("display");

    if (!displaySections.isEmpty()) {
        QDomElement display = displaySections.at(0).toElement(); // Assuming only one [display] section

        if (!display.isNull()) {
            // Check if "locale" attribute exists
            if (display.hasAttribute("locale")) {
                display.setAttribute("locale", unraidLangCode);
            } else {
                // Add "locale" attribute if not present
                display.setAttribute("locale", unraidLangCode);
            }
        }
    } else {
        // Add a new [display] section with the locale attribute
        QDomElement newDisplay = doc.createElement("display");
        newDisplay.setAttribute("locale", unraidLangCode);
        root.appendChild(newDisplay);
    }

    // Convert the modified XML back to a string
    QString newXml;
    QTextStream stream(&newXml);
    doc.save(stream, 4); // Indent with 4 spaces
    dataText = newXml;
}

}

if (dynamixCfg.open(QFile::WriteOnly | QFile::Truncate))
{
QTextStream out(&dynamixCfg);
out << dataText;
}
dynamixCfg.close();
}
}

// restore make bootable scripts and/or syslinux, if necessary
QDir dirTarget(folder);
if (dirTarget.mkdir("syslinux"))
Expand Down
180 changes: 173 additions & 7 deletions src/imagewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <QVersionNumber>
#include <QtNetwork>
#include <QDesktopServices>
#include <QTemporaryFile>
#ifndef QT_NO_WIDGETS
#include <QFileDialog>
#include <QApplication>
Expand Down Expand Up @@ -188,6 +189,7 @@ ImageWriter::ImageWriter(QObject *parent)
{
_currentLang = langname;
_currentLangcode = currentlangcode;
_unraidLangcode = _generateUnraidLangcode(currentlangcode, langname);
}
}
//_currentKeyboard = "us";
Expand Down Expand Up @@ -354,15 +356,24 @@ void ImageWriter::startWrite()
if (_multipleFilesInZip)
{
static_cast<DownloadExtractThread *>(_thread)->enableMultipleFileExtraction();
QString label{""};
if(_initFormat == "UNRAID") {
// if this is an unraid zip, the volume label needs to be UNRAID for the make bootable script to work as intended
label = "UNRAID";
// in order to properly propagate language selection to the unraid usb files themselves,
// we need to kick of a chain of events that starts with formatting the drive,
// which then kicks off a download of the available languages json,
// which then kicks off a download of the select language xml,
// which, if there is a zip file specified in that xml, then kicks off a download of that zip file,
// which then kicks of the download/extract of the actual unraid files
QString label{"UNRAID"}; // since this is an unraid zip, the volume label needs to be UNRAID for the make bootable script to work as intended
DriveFormatThread *dft = new DriveFormatThread(_dst.toLatin1(), label, this);
connect(dft, SIGNAL(error(QString)), SLOT(onError(QString)));
connect(dft, SIGNAL(success()), SLOT(onUnraidFormatComplete()));
dft->start();
} else {
DriveFormatThread *dft = new DriveFormatThread(_dst.toLatin1(), "", this);
connect(dft, SIGNAL(success()), _thread, SLOT(start()));
connect(dft, SIGNAL(error(QString)), SLOT(onError(QString)));
dft->start();
}
DriveFormatThread *dft = new DriveFormatThread(_dst.toLatin1(), label, this);
connect(dft, SIGNAL(success()), _thread, SLOT(start()));
connect(dft, SIGNAL(error(QString)), SLOT(onError(QString)));
dft->start();
}
else
{
Expand Down Expand Up @@ -1355,6 +1366,7 @@ void ImageWriter::changeLanguage(const QString &newLanguageName)
replaceTranslator(trans);
_currentLang = newLanguageName;
_currentLangcode = langcode;
_unraidLangcode = _generateUnraidLangcode(_currentLangcode, _currentLang);
}
else
{
Expand Down Expand Up @@ -1520,3 +1532,157 @@ bool ImageWriter::windowsBuild() {
return false;
#endif
}

QString ImageWriter::_generateUnraidLangcode(const QString& langcode, const QString& languageName)
{
QString unraidLangcode = langcode + "_";

if (langcode == "en") {
unraidLangcode.append("US");
}
else if (langcode == "pt") {
if (languageName.contains("Brasil")) {
unraidLangcode.append("BR");
}
else {
unraidLangcode.append("PT");
}
}
else if (langcode == "sv") {
unraidLangcode.append("SE");
}
else if (langcode == "uk") {
unraidLangcode.append("UA");
}
else if (langcode == "zh") {
unraidLangcode.append("CN");
}
else {
unraidLangcode.append(langcode.toUpper());
}
return unraidLangcode;
}

QByteArray ImageWriter::_parseUnraidLangJson(const QByteArray& jsonFilePath, const QString& unraidLangCode)
{
QByteArray jsonData = _readFileContents(jsonFilePath);
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);

qDebug() << parseError.errorString();
if (parseError.error != QJsonParseError::NoError) {
onError(parseError.errorString());
}

QJsonObject jsonObj = jsonDoc.object();

QJsonValue langVal = jsonObj.value(unraidLangCode);
QJsonObject langObj = langVal.toObject();

QJsonValue url = langObj.value("URL");
return url.toString().toUtf8();
}

QString ImageWriter::_parseUnraidLangXml(const QByteArray& xmlFilePath)
{
QByteArray xmlData = _readFileContents(xmlFilePath);
QXmlStreamReader xml(xmlData);
int count = 0;
QString found;

while (!xml.atEnd() && !xml.hasError()) {
QXmlStreamReader::TokenType token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
if (QString::compare(xml.name(), "LanguageURL") == 0) {
xml.readNext();
if (xml.atEnd()) {
break;
}

if (xml.isCharacters() && (QString::compare(xml.text(), "\n") != 0)) {
found.append(xml.text().toString());
}
}
}
}

return found;
}

QByteArray ImageWriter::_readFileContents(const QByteArray& filePath)
{
QFile fileIn(filePath);
if(!fileIn.open(QIODevice::ReadOnly)) {
onError("Error opening " + filePath);
}
// remove excess null characters (they cause the qt json parser to fail)
QByteArray data = fileIn.readAll().replace('\0', "");
fileIn.close();
if(!fileIn.open(QIODevice::WriteOnly)) {
onError("Error opening " + filePath);
}
fileIn.write(data);
return data;
}

void ImageWriter::onUnraidFormatComplete()
{
QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString download_dir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
_unraidLangJsonTmpPath = (download_dir + "/unraid-usb-creator-" + uuid + ".tmp").toUtf8();
QFile langFile(_unraidLangJsonTmpPath);
langFile.open(QIODevice::ReadWrite);

setSetting("imagecustomization/UNRAID_LANG_JSON", _unraidLangJsonTmpPath);
DownloadThread * dl_thread = new DownloadThread(_unraidLangJsonUrl, _unraidLangJsonTmpPath, "", this);
connect(dl_thread, SIGNAL(error(QString)), SLOT(onError(QString)));
connect(dl_thread, SIGNAL(success()), SLOT(onUnraidJsonDownloadComplete()));
dl_thread->start();
}

void ImageWriter::onUnraidJsonDownloadComplete()
{
QByteArray xmlUrl = _parseUnraidLangJson(_unraidLangJsonTmpPath, _unraidLangcode);

QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString download_dir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
_unraidLangXmlTmpPath = (download_dir + "/unraid-usb-creator-" + uuid + ".tmp").toUtf8();
QFile langFile(_unraidLangXmlTmpPath);
langFile.open(QIODevice::ReadWrite);

setSetting("imagecustomization/UNRAID_LANG_XML", _unraidLangXmlTmpPath);
DownloadThread * dl_thread = new DownloadThread(xmlUrl, _unraidLangXmlTmpPath, "", this);
connect(dl_thread, SIGNAL(error(QString)), SLOT(onError(QString)));
connect(dl_thread, SIGNAL(success()), SLOT(onUnraidXmlDownloadComplete()));
dl_thread->start();
}

void ImageWriter::onUnraidXmlDownloadComplete()
{
QString zipUrl = _parseUnraidLangXml(_unraidLangXmlTmpPath);
setSetting("imagecustomization/UNRAID_LANG_CODE", _unraidLangcode);
if(zipUrl.isEmpty())
{
// this has to be called again so getSavedCustomizationSettings returns the updated qvariantmap
_thread->setImageCustomization(_config, _cmdline, _firstrun, _cloudinit, _cloudinitNetwork, _initFormat, getSavedCustomizationSettings());
_thread->start();
}
else
{
QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString download_dir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
_unraidLangZipTmpPath = (download_dir + "/unraid-usb-creator-" + uuid + ".tmp").toUtf8();
QFile langFile(_unraidLangZipTmpPath);
langFile.open(QIODevice::ReadWrite);


setSetting("imagecustomization/UNRAID_LANG_ZIP", _unraidLangZipTmpPath);
// this has to be called again so getSavedCustomizationSettings returns the updated qvariantmap
_thread->setImageCustomization(_config, _cmdline, _firstrun, _cloudinit, _cloudinitNetwork, _initFormat, getSavedCustomizationSettings());
DownloadThread * dl_thread = new DownloadThread(zipUrl.toUtf8(), _unraidLangZipTmpPath, "", this);
connect(dl_thread, SIGNAL(error(QString)), SLOT(onError(QString)));
connect(dl_thread, SIGNAL(success()), _thread, SLOT(start()));
dl_thread->start();
}
}

14 changes: 13 additions & 1 deletion src/imagewriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <QUrl>
#include <QSettings>
#include <QVariant>
#include <QTemporaryFile>
#include "config.h"
#include "powersaveblocker.h"
#include "drivelistmodel.h"
Expand Down Expand Up @@ -183,6 +184,9 @@ protected slots:
void onPreparationStatusUpdate(QString msg);
void handleNetworkRequestFinished(QNetworkReply *data);
void onSTPdetected();
void onUnraidFormatComplete();
void onUnraidJsonDownloadComplete();
void onUnraidXmlDownloadComplete();

private:
// Recursively walk all the entries with subitems and, for any which
Expand All @@ -195,7 +199,7 @@ protected slots:

protected:
QUrl _src, _repo;
QString _dst, _cacheFileName, _parentCategory, _osName, _currentLang, _currentLangcode, _currentKeyboard;
QString _dst, _cacheFileName, _parentCategory, _osName, _currentLang, _currentLangcode, _currentKeyboard, _unraidLangcode;
QByteArray _expectedHash, _cachedFileHash, _cmdline, _config, _firstrun, _cloudinit, _cloudinitNetwork, _initFormat;
quint64 _downloadLen, _extrLen, _devLen, _dlnow, _verifynow;
DriveListModel _drivelist;
Expand All @@ -212,13 +216,21 @@ protected slots:
QWinTaskbarButton *_taskbarButton;
#endif
bool _guidValid;
const QByteArray _unraidLangJsonUrl{"https://assets.ca.unraid.net/feed/languageSelection.json"};
QByteArray _unraidLangJsonTmpPath;
QByteArray _unraidLangXmlTmpPath;
QByteArray _unraidLangZipTmpPath;

void _parseCompressedFile();
void _parseXZFile();
QString _pubKeyFileName();
QString _privKeyFileName();
QString _sshKeyDir();
QString _sshKeyGen();
QString _generateUnraidLangcode(const QString& langcode, const QString& languageName);
QByteArray _readFileContents(const QByteArray& filePath);
QString _parseUnraidLangXml(const QByteArray& xmlFilePath);
QByteArray _parseUnraidLangJson(const QByteArray& jsonFilePath, const QString& unraidLangCode);
};

#endif // IMAGEWRITER_H
2 changes: 1 addition & 1 deletion src/qmlcomponents/regex_validator_qt6.qml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import QtQuick
import QtQuick 2.9

RegularExpressionValidator {
regularExpression: /^[A-Za-z0-9]([A-Za-z0-9\-\.]{0,13}[A-Za-z0-9])?$/
Expand Down
Loading