mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-18 16:05:21 +01:00
parent
258f9d77aa
commit
62e9969afb
9 changed files with 307 additions and 10 deletions
|
@ -19,7 +19,7 @@ set(CMAKE_AUTOMOC ON)
|
|||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
# Adding headers explicity so they are displayed in Qt Creator
|
||||
set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h drivelistmodelpollthread.h driveformatthread.h powersaveblocker.h
|
||||
set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h drivelistmodelpollthread.h driveformatthread.h powersaveblocker.h cli.h
|
||||
downloadthread.h downloadextractthread.h localfileextractthread.h downloadstatstelemetry.h dependencies/mountutils/src/mountutils.hpp dependencies/sha256crypt/sha256crypt.h)
|
||||
|
||||
# Add dependencies
|
||||
|
@ -69,7 +69,7 @@ endif()
|
|||
|
||||
set(SOURCES "main.cpp" "imagewriter.cpp" "networkaccessmanagerfactory.cpp"
|
||||
"drivelistitem.cpp" "drivelistmodel.cpp" "drivelistmodelpollthread.cpp" "downloadthread.cpp" "downloadextractthread.cpp"
|
||||
"driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc" "dependencies/sha256crypt/sha256crypt.c")
|
||||
"driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc" "dependencies/sha256crypt/sha256crypt.c" "cli.cpp")
|
||||
|
||||
find_package(Qt5 COMPONENTS Core Quick LinguistTools Svg OPTIONAL_COMPONENTS Widgets)
|
||||
if (Qt5Widgets_FOUND)
|
||||
|
@ -157,7 +157,8 @@ if (WIN32)
|
|||
add_custom_command(TARGET ${PROJECT_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.exe" "${CMAKE_BINARY_DIR}/dependencies/fat32format/fat32format.exe" "${CMAKE_SOURCE_DIR}/license.txt"
|
||||
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.exe" "${CMAKE_BINARY_DIR}/dependencies/fat32format/fat32format.exe"
|
||||
"${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_SOURCE_DIR}/windows/rpi-imager-cli.cmd"
|
||||
"${CMAKE_BINARY_DIR}/deploy")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME}
|
||||
|
|
224
cli.cpp
Normal file
224
cli.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
|
||||
*/
|
||||
|
||||
#include "cli.h"
|
||||
#include "imagewriter.h"
|
||||
#include <iostream>
|
||||
#include <QCoreApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QFileInfo>
|
||||
#include "drivelistmodel.h"
|
||||
#include "dependencies/drivelist/src/drivelist.hpp"
|
||||
|
||||
/* Message handler to discard qDebug() output if using cli (unless --debug is set) */
|
||||
static void devnullMsgHandler(QtMsgType, const QMessageLogContext &, const QString &)
|
||||
{
|
||||
}
|
||||
|
||||
Cli::Cli(int &argc, char *argv[]) : QObject(nullptr)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
/* Allocate console on Windows (only needed if compiled as GUI program) */
|
||||
if (::AttachConsole(ATTACH_PARENT_PROCESS) || ::AllocConsole())
|
||||
{
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
std::ios::sync_with_stdio();
|
||||
}
|
||||
#endif
|
||||
_app = new QCoreApplication(argc, argv);
|
||||
_app->setOrganizationName("Raspberry Pi");
|
||||
_app->setOrganizationDomain("raspberrypi.org");
|
||||
_app->setApplicationName("Imager");
|
||||
_imageWriter = new ImageWriter;
|
||||
connect(_imageWriter, &ImageWriter::success, this, &Cli::onSuccess);
|
||||
connect(_imageWriter, &ImageWriter::error, this, &Cli::onError);
|
||||
connect(_imageWriter, &ImageWriter::preparationStatusUpdate, this, &Cli::onPreparationStatusUpdate);
|
||||
connect(_imageWriter, &ImageWriter::downloadProgress, this, &Cli::onDownloadProgress);
|
||||
connect(_imageWriter, &ImageWriter::verifyProgress, this, &Cli::onVerifyProgress);
|
||||
}
|
||||
|
||||
Cli::~Cli()
|
||||
{
|
||||
delete _imageWriter;
|
||||
delete _app;
|
||||
}
|
||||
|
||||
int Cli::main()
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption cli("cli");
|
||||
parser.addOption(cli);
|
||||
QCommandLineOption disableVerify("disable-verify", "Disable verification");
|
||||
parser.addOption(disableVerify);
|
||||
QCommandLineOption writeSystemDrive("enable-writing-system-drives", "Only use this if you know what you are doing");
|
||||
parser.addOption(writeSystemDrive);
|
||||
QCommandLineOption sha256Option("sha256", "Expected hash", "sha256", "");
|
||||
parser.addOption(sha256Option);
|
||||
QCommandLineOption debugOption("debug", "Output debug messages to console");
|
||||
parser.addOption(debugOption);
|
||||
QCommandLineOption quietOption("quiet", "Only write to console on error");
|
||||
parser.addOption(quietOption);
|
||||
|
||||
parser.addPositionalArgument("src", "Image file/URL");
|
||||
parser.addPositionalArgument("dst", "Destination device");
|
||||
parser.process(*_app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.count() != 2)
|
||||
{
|
||||
std::cerr << "Usage: --cli [--disable-verify] [--sha256 <expected hash>] [--debug] [--quiet] <image file to write> <destination drive device>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!parser.isSet(debugOption))
|
||||
{
|
||||
qInstallMessageHandler(devnullMsgHandler);
|
||||
}
|
||||
_quiet = parser.isSet(quietOption);
|
||||
|
||||
if (args[0].startsWith("http:", Qt::CaseInsensitive) || args[0].startsWith("https:", Qt::CaseInsensitive))
|
||||
{
|
||||
_imageWriter->setSrc(args[0], 0, 0, parser.value(sha256Option).toLatin1() );
|
||||
}
|
||||
else
|
||||
{
|
||||
QFileInfo fi(args[0]);
|
||||
|
||||
if (fi.isFile())
|
||||
{
|
||||
_imageWriter->setSrc(QUrl::fromLocalFile(args[0]), fi.size(), 0, parser.value(sha256Option).toLatin1() );
|
||||
}
|
||||
else if (!fi.exists())
|
||||
{
|
||||
std::cerr << "Error: source file does not exists" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: source is not a regular file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.isSet(writeSystemDrive))
|
||||
{
|
||||
std::cerr << "WARNING: writing to system drives is enabled." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
DriveListModel dlm;
|
||||
dlm.processDriveList(Drivelist::ListStorageDevices() );
|
||||
bool foundDrive = false;
|
||||
int numDrives = dlm.rowCount( QModelIndex() );
|
||||
|
||||
for (int i = 0; i < numDrives; i++)
|
||||
{
|
||||
if (dlm.index(i, 0).data(dlm.deviceRole) == args[1])
|
||||
{
|
||||
foundDrive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDrive)
|
||||
{
|
||||
std::cerr << "Destination drive is not in list of removable volumes. Choose one of the following:" << std::endl << std::endl;
|
||||
|
||||
for (int i = 0; i < numDrives; i++)
|
||||
{
|
||||
QModelIndex idx = dlm.index(i, 0);
|
||||
QByteArray line = idx.data(dlm.deviceRole).toByteArray()+" ("+idx.data(dlm.descriptionRole).toByteArray()+")";
|
||||
|
||||
std::cerr << line.constData() << std::endl;
|
||||
}
|
||||
|
||||
std::cerr << std::endl << "Or use --enable-writing-system-drives to overrule." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
_imageWriter->setDst(args[1]);
|
||||
_imageWriter->setVerifyEnabled(!parser.isSet(disableVerify));
|
||||
|
||||
/* Run startWrite() in event loop (otherwise calling _app->exit() on error does not work) */
|
||||
QTimer::singleShot(1, _imageWriter, &ImageWriter::startWrite);
|
||||
return _app->exec();
|
||||
}
|
||||
|
||||
void Cli::onSuccess()
|
||||
{
|
||||
if (!_quiet)
|
||||
{
|
||||
_clearLine();
|
||||
std::cerr << "Write successful." << std::endl;
|
||||
}
|
||||
_app->exit(0);
|
||||
}
|
||||
|
||||
void Cli::_clearLine()
|
||||
{
|
||||
/* Properly clearing line requires platform specific code.
|
||||
Just write some spaces for now, and return to beginning of line. */
|
||||
std::cerr << " \r";
|
||||
}
|
||||
|
||||
void Cli::onError(QVariant msg)
|
||||
{
|
||||
QByteArray m = msg.toByteArray();
|
||||
|
||||
if (!_quiet)
|
||||
{
|
||||
_clearLine();
|
||||
}
|
||||
std::cerr << "Error: " << m.constData() << std::endl;
|
||||
_app->exit(1);
|
||||
}
|
||||
|
||||
void Cli::onDownloadProgress(QVariant dlnow, QVariant dltotal)
|
||||
{
|
||||
_printProgress("Writing", dlnow, dltotal);
|
||||
}
|
||||
|
||||
void Cli::onVerifyProgress(QVariant now, QVariant total)
|
||||
{
|
||||
_printProgress("Verifying", now, total);
|
||||
}
|
||||
|
||||
void Cli::onPreparationStatusUpdate(QVariant msg)
|
||||
{
|
||||
if (!_quiet)
|
||||
{
|
||||
QByteArray ascii = QByteArray(" ")+msg.toByteArray()+"\r";
|
||||
_clearLine();
|
||||
std::cerr << ascii.constData();
|
||||
}
|
||||
}
|
||||
|
||||
void Cli::_printProgress(const QByteArray &msg, QVariant now, QVariant total)
|
||||
{
|
||||
if (_quiet)
|
||||
return;
|
||||
|
||||
float n = now.toFloat();
|
||||
float t = total.toFloat();
|
||||
|
||||
if (t)
|
||||
{
|
||||
int percent = n/t*100;
|
||||
if (percent != _lastPercent || msg != _lastMsg)
|
||||
{
|
||||
QByteArray txt = QByteArray(" ")+msg+": ["+QByteArray(percent/5, '-')+'>'+QByteArray(20-percent/5, ' ')+"] "+QByteArray::number(percent)+" %\r";
|
||||
std::cerr << txt.constData();
|
||||
_lastPercent = percent;
|
||||
_lastMsg = msg;
|
||||
}
|
||||
}
|
||||
else if (msg != _lastMsg)
|
||||
{
|
||||
std::cerr << msg.constData() << "\r";
|
||||
_lastMsg = msg;
|
||||
}
|
||||
}
|
39
cli.h
Normal file
39
cli.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef CLI_H
|
||||
#define CLI_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
class ImageWriter;
|
||||
class QCoreApplication;
|
||||
|
||||
class Cli : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Cli(int &argc, char *argv[]);
|
||||
virtual ~Cli();
|
||||
int main();
|
||||
|
||||
protected:
|
||||
QCoreApplication *_app;
|
||||
ImageWriter *_imageWriter;
|
||||
int _lastPercent;
|
||||
QByteArray _lastMsg;
|
||||
bool _quiet;
|
||||
|
||||
void _printProgress(const QByteArray &msg, QVariant now, QVariant total);
|
||||
void _clearLine();
|
||||
|
||||
protected slots:
|
||||
void onSuccess();
|
||||
void onError(QVariant msg);
|
||||
void onDownloadProgress(QVariant dlnow, QVariant dltotal);
|
||||
void onVerifyProgress(QVariant now, QVariant total);
|
||||
void onPreparationStatusUpdate(QVariant msg);
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // CLI_H
|
|
@ -747,8 +747,8 @@ bool DownloadThread::_verify()
|
|||
}
|
||||
qFreeAligned(verifyBuf);
|
||||
|
||||
qDebug() << "Verify done in" << t1.elapsed() / 1000.0 << "seconds";
|
||||
qDebug() << "Verify hash:" << _verifyhash.result().toHex();
|
||||
qDebug() << "Verify done in" << t1.elapsed() / 1000.0 << "seconds";
|
||||
|
||||
if (_verifyhash.result() == _writehash.result() || !_verifyEnabled || _cancelled)
|
||||
{
|
||||
|
|
|
@ -63,7 +63,16 @@ ImageWriter::ImageWriter(QObject *parent)
|
|||
{
|
||||
connect(&_polltimer, SIGNAL(timeout()), SLOT(pollProgress()));
|
||||
|
||||
QString platform = QGuiApplication::platformName();
|
||||
QString platform;
|
||||
if (qobject_cast<QGuiApplication*>(QCoreApplication::instance()) )
|
||||
{
|
||||
platform = QGuiApplication::platformName();
|
||||
}
|
||||
else
|
||||
{
|
||||
platform = "cli";
|
||||
}
|
||||
|
||||
if (platform == "eglfs" || platform == "linuxfb")
|
||||
{
|
||||
_embeddedMode = true;
|
||||
|
@ -469,7 +478,7 @@ void ImageWriter::onSuccess()
|
|||
emit success();
|
||||
|
||||
#ifndef QT_NO_WIDGETS
|
||||
if (_settings.value("beep").toBool())
|
||||
if (_settings.value("beep").toBool() && qobject_cast<QApplication*>(QCoreApplication::instance()) )
|
||||
{
|
||||
QApplication::beep();
|
||||
}
|
||||
|
@ -482,7 +491,7 @@ void ImageWriter::onError(QString msg)
|
|||
emit error(msg);
|
||||
|
||||
#ifndef QT_NO_WIDGETS
|
||||
if (_settings.value("beep").toBool())
|
||||
if (_settings.value("beep").toBool() && qobject_cast<QApplication*>(QCoreApplication::instance()) )
|
||||
QApplication::beep();
|
||||
#endif
|
||||
}
|
||||
|
@ -1014,5 +1023,6 @@ bool ImageWriter::hasSavedCustomizationSettings()
|
|||
}
|
||||
|
||||
void MountUtilsLog(std::string msg) {
|
||||
qDebug() << "mountutils:" << msg.c_str();
|
||||
Q_UNUSED(msg)
|
||||
//qDebug() << "mountutils:" << msg.c_str();
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ QString UDisks2Api::_resolveDevice(const QString &device)
|
|||
|
||||
void UDisks2Api::_unmountDrive(const QString &driveDbusPath)
|
||||
{
|
||||
qDebug() << "Drive:" << driveDbusPath;
|
||||
//qDebug() << "Drive:" << driveDbusPath;
|
||||
|
||||
QDBusInterface manager("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/Manager",
|
||||
"org.freedesktop.UDisks2.Manager", QDBusConnection::systemBus());
|
||||
|
@ -81,7 +81,7 @@ void UDisks2Api::_unmountDrive(const QString &driveDbusPath)
|
|||
if (driveOfDev != driveDbusPath)
|
||||
continue;
|
||||
|
||||
qDebug() << "Device:" << devpathStr << "belongs to same drive";
|
||||
//qDebug() << "Device:" << devpathStr << "belongs to same drive";
|
||||
QDBusInterface filesystem("org.freedesktop.UDisks2", devpathStr,
|
||||
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
|
||||
|
||||
|
|
12
main.cpp
12
main.cpp
|
@ -13,6 +13,7 @@
|
|||
#include "imagewriter.h"
|
||||
#include "drivelistmodel.h"
|
||||
#include "networkaccessmanagerfactory.h"
|
||||
#include "cli.h"
|
||||
#include <QMessageLogContext>
|
||||
#include <QQuickWindow>
|
||||
#include <QTranslator>
|
||||
|
@ -36,6 +37,16 @@ static void consoleMsgHandler(QtMsgType, const QMessageLogContext &, const QStri
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "--cli") == 0)
|
||||
{
|
||||
/* CLI mode */
|
||||
Cli cli(argc, argv);
|
||||
return cli.main();
|
||||
}
|
||||
}
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
#ifdef Q_OS_WIN
|
||||
// prefer ANGLE (DirectX) over desktop OpenGL
|
||||
|
@ -149,6 +160,7 @@ int main(int argc, char *argv[])
|
|||
else if (args[i] == "--help")
|
||||
{
|
||||
cerr << args[0] << " [--debug] [--version] [--repo <repository URL>] [--qm <custom qm translation file>] [--disable-telemetry] [<image file to write>]" << endl;
|
||||
cerr << "-OR- " << args[0] << " --cli [--disable-verify] [--sha256 <expected hash>] [--debug] [--quiet] <image file to write> <destination drive device>" << endl;
|
||||
return 0;
|
||||
}
|
||||
else if (args[i] == "--version")
|
||||
|
|
9
windows/rpi-imager-cli.cmd
Normal file
9
windows/rpi-imager-cli.cmd
Normal file
|
@ -0,0 +1,9 @@
|
|||
@echo off
|
||||
|
||||
rem
|
||||
rem For scripting: call rpi-imager.exe and wait until it finished before continuing
|
||||
rem This is necessary because it is compiled as GUI application, and Windows
|
||||
rem normalling does not wait until those exit
|
||||
rem
|
||||
|
||||
start /WAIT rpi-imager.exe --cli %*
|
|
@ -244,6 +244,7 @@ File "deploy\Qt5Svg.dll"
|
|||
File "deploy\Qt5Widgets.dll"
|
||||
File "deploy\Qt5WinExtras.dll"
|
||||
File "deploy\rpi-imager.exe"
|
||||
File "deploy\rpi-imager-cli.cmd"
|
||||
SetOutPath "$INSTDIR\styles"
|
||||
File "deploy\styles\qwindowsvistastyle.dll"
|
||||
SetOutPath "$INSTDIR\QtQuick.2"
|
||||
|
@ -700,6 +701,7 @@ Delete "$INSTDIR\Qt5WinExtras.dll"
|
|||
# Old name
|
||||
Delete "$INSTDIR\imagingutility.exe"
|
||||
Delete "$INSTDIR\rpi-imager.exe"
|
||||
Delete "$INSTDIR\rpi-imager-cli.cmd"
|
||||
Delete "$INSTDIR\styles\qwindowsvistastyle.dll"
|
||||
Delete "$INSTDIR\QtQuick.2\plugins.qmltypes"
|
||||
Delete "$INSTDIR\QtQuick.2\qmldir"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue